Example #1
0
// NewCheckUpdate is the required initializer for CheckUpdate.
func NewCheckUpdate() *CheckUpdate {
	return &CheckUpdate{
		LocalVersion:       config.VersionNum(),
		Location:           config.Konfig.Endpoints.KDLatest.Public.String(),
		RandomSeededNumber: rand.Intn(3),
		ForceCheck:         false,
	}
}
Example #2
0
func (r *RunCommand) runOnRemote(localPath string, cmdWithArgsStr string) (*ExecRes, error) {
	machine, err := mountcli.NewMountcli().FindMountNameByPath(localPath)
	if err != nil {
		return nil, err
	}

	fullCmdPath, err := r.getCmdRemotePath(machine, localPath)
	if err != nil {
		return nil, err
	}

	// track metrics
	metrics.TrackRun(machine, config.VersionNum())

	return r.runOnMachine(machine, fullCmdPath, cmdWithArgsStr)
}
Example #3
0
// Unmount tells klient to unmount the given name and path.
func (c *UnmountCommand) Unmount(name, path string) error {
	req := req.UnmountFolder{
		Name:      name,
		LocalPath: path,
	}

	// currently there's no return response to care about
	if _, err := c.Klient.Tell("remote.unmountFolder", req); err != nil {
		return err
	}

	// track metrics
	metrics.TrackUnmount(name, config.VersionNum())

	return nil
}
Example #4
0
// Run the Mount command
func (c *MountCommand) Run() (int, error) {
	// Cleanup local folder when command exists with failure.
	cleanupPath := false
	defer func() {
		if cleanupPath {
			c.cleanupPath()
		}
	}()

	if c.Options.Debug {
		c.Log.SetLevel(logging.DEBUG)
	}

	// allow scp like declaration, ie `<machine name>:/path/to/remote`
	if strings.Contains(c.Options.Name, ":") {
		names := strings.SplitN(c.Options.Name, ":", 2)
		c.Options.Name, c.Options.RemotePath = names[0], names[1]
	}

	// setup our klient, if needed
	if exit, err := c.setupKlient(); err != nil {
		return exit, err
	}

	// Get the machine name from a partial name, if needed.
	if err := c.findMachineName(); err != nil {
		return 1, err
	}

	// Decide smart options, if needed, before the rest of
	// the flow.
	if err := c.smartOptions(); err != nil {
		return 1, err
	}

	if exit, err := c.handleOptions(); err != nil {
		return exit, err
	}

	// create the mount dir if needed
	if err := c.createMountDir(); err != nil {
		return 1, err
	}

	// If they choose not to use fuse, mount with only caching.
	if c.Options.OneWaySync {
		if err := c.useSync(); err != nil {
			cleanupPath = true
			return 1, err
		}
	}

	if c.Options.PrefetchAll {
		if err := c.prefetchAll(); err != nil {
			cleanupPath = true

			return 1, fmt.Errorf("failed to prefetch: %s", err)
		}
	}

	mountRequest := req.MountFolder{
		Debug:           c.Options.Debug,
		Name:            c.Options.Name,
		LocalPath:       c.Options.LocalPath,
		RemotePath:      c.Options.RemotePath,
		NoIgnore:        c.Options.NoIgnore,
		NoPrefetchMeta:  c.Options.NoPrefetchMeta,
		PrefetchAll:     c.Options.PrefetchAll,
		NoWatch:         c.Options.NoWatch,
		CachePath:       getCachePath(c.Options.Name),
		Trace:           c.Options.Trace,
		OneWaySyncMount: c.Options.OneWaySync,
	}

	// Actually mount the folder. Errors are printed by the mountFolder func to the user.
	if err := c.mountFolder(mountRequest); err != nil {
		cleanupPath = true

		return 1, err
	}

	// track metrics
	o := map[string]interface{}{
		"no-ignore":        c.Options.NoIgnore,
		"no-prefetch-meta": c.Options.NoPrefetchMeta,
		"prefetch-all":     c.Options.PrefetchAll,
		"oneway-sync":      c.Options.OneWaySync,
		"no-watch":         c.Options.NoWatch,
		"version":          config.VersionNum(),
	}
	metrics.TrackMount(c.Options.Name, c.Options.LocalPath, o)

	c.printfln("Mount complete.")

	return 0, nil
}
Example #5
0
// The implementation of InstallCommandFactory, with an error return. This
// allows us to track the error metrics.
func InstallCommandFactory(c *cli.Context, log logging.Logger, _ string) (exit int, err error) {
	if len(c.Args()) != 1 {
		cli.ShowCommandHelp(c, "install")
		return 1, errors.New("incorrect cli usage: no args")
	}

	// Now that we created the logfile, set our logger handler to use that newly created
	// file, so that we can log errors during installation.
	f, err := createLogFile(LogFilePath)
	if err != nil {
		fmt.Println(`Error: Unable to open log files.`)
	} else {
		log.SetHandler(logging.NewWriterHandler(f))
		log.Info("Installation created log file at %q", LogFilePath)
	}

	// Track all failed installations.
	defer func() {
		if err != nil {
			log.Error(err.Error())
			metrics.TrackInstallFailed(err.Error(), config.VersionNum())
		}
	}()

	authToken := c.Args().Get(0)

	// We need to check if the authToken is somehow empty, because klient
	// will default to user/pass if there is no auth token (despite setting
	// the token flag)
	if strings.TrimSpace(authToken) == "" {
		cli.ShowCommandHelp(c, "install")
		return 1, errors.New("incorrect cli usage: missing token")
	}

	// Create the installation dir, if needed.
	if err := os.MkdirAll(KlientDirectory, 0755); err != nil {
		log.Error(
			"Error creating klient binary directory(s). path:%s, err:%s",
			KlientDirectory, err,
		)
		fmt.Println(FailedInstallingKlient)
		return 1, fmt.Errorf("failed creating klient binary: %s", err)
	}

	klientBinPath := filepath.Join(KlientDirectory, "klient")

	// TODO: Accept `kd install --user foo` flag to replace the
	// environ checking.
	klientSh := klientSh{
		User:          konfig.CurrentUser.Username,
		KlientBinPath: klientBinPath,
	}

	if err := klientSh.Create(filepath.Join(KlientDirectory, "klient.sh")); err != nil {
		err = fmt.Errorf("error writing klient.sh file: %s", err)
		fmt.Println(FailedInstallingKlient)
		return 1, err
	}

	fmt.Println("Downloading...")

	version, err := latestVersion(config.Konfig.Endpoints.KlientLatest.Public.String())
	if err != nil {
		fmt.Printf(FailedDownloadingKlient)
		return 1, fmt.Errorf("error getting latest klient version: %s", err)
	}

	if err := downloadRemoteToLocal(config.S3Klient(version, config.Environment), klientBinPath); err != nil {
		fmt.Printf(FailedDownloadingKlient)
		return 1, fmt.Errorf("error downloading klient binary: %s", err)
	}

	fmt.Printf("Created %s\n", klientBinPath)
	fmt.Printf(`Authenticating you to the %s

`, config.KlientName)

	cmd := exec.Command(klientBinPath, "-register",
		"-token", authToken,
		"--kontrol-url", strings.TrimSpace(c.String("kontrol")),
	)

	var errBuf bytes.Buffer

	// Note that we are *only* printing to Stdout. This is done because
	// Klient logs error messages to Stderr, and we want to control the UX for
	// that interaction.
	//
	// TODO: Logg Klient's Stderr message on error, if any.
	cmd.Stdout = os.Stdout
	cmd.Stdin = os.Stdin
	cmd.Stderr = &errBuf

	if err := cmd.Run(); err != nil {
		err = fmt.Errorf("error registering klient: %s, klient stderr: %s", err, errBuf.String())
		fmt.Println(FailedRegisteringKlient)
		return 1, err
	}

	// Best-effort attempts at fixinig permissions and ownership, ignore any errors.
	_ = configstore.FixOwner()

	opts := &ServiceOptions{
		Username: klientSh.User,
	}

	// Create our interface to the OS specific service
	s, err := newService(opts)
	if err != nil {
		fmt.Println(GenericInternalNewCodeError)
		return 1, fmt.Errorf("error creating Service: %s", err)
	}

	// try to uninstall first, otherwise Install may fail if
	// klient.plist or klient init script already exist
	s.Uninstall()

	// Install the klient binary as a OS service
	if err := s.Install(); err != nil {
		fmt.Println(GenericInternalNewCodeError)
		return 1, fmt.Errorf("error installing Service: %s", err)
	}

	// Tell the service to start. Normally it starts automatically, but
	// if the user told the service to stop (previously), it may not
	// start automatically.
	//
	// Note that the service may error if it is already running, so
	// we're ignoring any starting errors here. We will verify the
	// connection below, anyway.
	if err := s.Start(); err != nil {
		fmt.Println(FailedStartKlient)
		return 1, fmt.Errorf("error starting klient service: %s", err)
	}

	fmt.Println("Verifying installation...")
	err = WaitUntilStarted(config.Konfig.Endpoints.Klient.Private.String(), CommandAttempts, CommandWaitTime)

	// After X times, if err != nil we failed to connect to klient.
	// Inform the user.
	if err != nil {
		fmt.Println(FailedInstallingKlient)
		return 1, fmt.Errorf("error verifying the installation of klient: %s", err)
	}

	// Best-effort attempts at fixinig permissions and ownership, ignore any errors.
	_ = uploader.FixPerms()

	// track metrics
	metrics.TrackInstall(config.VersionNum())

	fmt.Printf("\n\nSuccessfully installed and started the %s!\n", config.KlientName)

	return 0, nil
}
Example #6
0
// SSHCommandFactory is the factory method for SSHCommand.
func SSHCommandFactory(c *cli.Context, log logging.Logger, _ string) int {
	if len(c.Args()) != 1 {
		cli.ShowCommandHelp(c, "ssh")
		return 1
	}

	if c.Bool("debug") {
		log.SetLevel(logging.DEBUG)
	}

	opts := ssh.SSHCommandOpts{
		Debug:          c.Bool("debug") || config.Konfig.Debug,
		RemoteUsername: c.String("username"),
		Ask:            true,
	}
	cmd, err := ssh.NewSSHCommand(log, opts)
	mountName := c.Args()[0]

	// TODO: Refactor SSHCommand instance to require no initialization,
	// and thus avoid needing to log an error in a weird place.
	if err != nil {
		log.Error("Error initializing ssh: %s", err)
		switch err {
		case ssh.ErrLocalDialingFailed:
			fmt.Println(
				defaultHealthChecker.CheckAllFailureOrMessagef(KlientIsntRunning),
			)
		default:
			fmt.Println(GenericInternalError)
		}

		metrics.TrackSSHFailed(mountName, err.Error(), config.VersionNum())
		return 1
	}

	now := time.Now()

	// track metrics
	go func() {
		metrics.TrackSSH(mountName, config.VersionNum())
	}()

	err = cmd.Run(mountName)
	switch err {
	case nil:
		metrics.TrackSSHEnd(mountName, "", -now.Sub(now).Minutes(), config.VersionNum())
		return 0
	case ssh.ErrMachineNotFound:
		fmt.Println(MachineNotFound)
	case ssh.ErrCannotFindUser:
		fmt.Println(CannotFindSSHUser)
		metrics.TrackSSHFailed(mountName, err.Error(), config.VersionNum())
	case ssh.ErrFailedToGetSSHKey:
		fmt.Println(FailedGetSSHKey)
		metrics.TrackSSHFailed(mountName, err.Error(), config.VersionNum())
	case ssh.ErrMachineNotValidYet:
		fmt.Println(defaultHealthChecker.CheckAllFailureOrMessagef(MachineNotValidYet))
		metrics.TrackSSHFailed(mountName, err.Error(), config.VersionNum())
	case ssh.ErrRemoteDialingFailed:
		fmt.Println(defaultHealthChecker.CheckAllFailureOrMessagef(FailedDialingRemote))
		metrics.TrackSSHFailed(mountName, err.Error(), config.VersionNum())
	case shortcut.ErrMachineNotFound:
		fmt.Println(MachineNotFound)
		metrics.TrackSSHFailed(mountName, err.Error(), config.VersionNum())
	}

	log.Error("SSHCommand.Run returned err:%s", err)

	// ssh returns `exit status 255` on disconnection; so we also send how long
	// session has been running for to indicate if ssh was successful at least
	// once and the failed due to disconnection
	metrics.TrackSSHEnd(mountName, err.Error(), -now.Sub(now).Minutes(), config.VersionNum())

	return 1
}