Example #1
0
// NewSSHCommand is the required initializer for SSHCommand.
func NewSSHCommand(log logging.Logger, opts SSHCommandOpts) (*SSHCommand, error) {
	usr, err := user.Current()
	if err != nil {
		return nil, err
	}

	klientKite, err := klient.CreateKlientWithDefaultOpts()
	if err != nil {
		return nil, err
	}

	if err := klientKite.Dial(); err != nil {
		log.New("NewSSHCommand").Error("Dialing local klient failed. err:%s", err)
		return nil, ErrLocalDialingFailed
	}

	k := klient.NewKlient(klientKite)

	return &SSHCommand{
		Klient: k,
		Log:    log.New("SSHCommand"),
		Ask:    opts.Ask,
		Debug:  opts.Debug,
		SSHKey: &SSHKey{
			Log:            log.New("SSHKey"),
			Debug:          opts.Debug,
			RemoteUsername: opts.RemoteUsername,
			KeyPath:        path.Join(usr.HomeDir, config.SSHDefaultKeyDir),
			KeyName:        config.SSHDefaultKeyName,
			Klient:         k,
		},
	}, nil
}
Example #2
0
// CheckLocal runs several diagnostics on the local Klient. Errors
// indicate an unhealthy or not running Klient, and can be compare to
// the ErrHealth* types.
//
// TODO: Possibly return a set of warnings too? If we have any..
func (c *HealthChecker) LocalRequirements() error {
	res, err := c.HTTPClient.Get(c.LocalKlientAddress)
	// If there was an error even talking to Klient, something is wrong.
	if err != nil {
		return ErrHealthNoHTTPReponse{Message: fmt.Sprintf(
			"local klient /kite route is returning an error: %s", err,
		)}
	}
	defer res.Body.Close()

	switch res.StatusCode {
	case http.StatusOK, http.StatusNoContent:
	default:
		return ErrHealthUnexpectedResponse{Message: fmt.Sprintf(
			"unexpected status code: %d", res.StatusCode,
		)}
	}

	if res.StatusCode == http.StatusOK {
		// It should be safe to ignore any errors dumping the response data,
		// since we just want to check the data itself. Handling the error
		// might aid with debugging any problems though.
		p, err := ioutil.ReadAll(res.Body)
		if err != nil {
			return ErrHealthUnexpectedResponse{Message: fmt.Sprintf(
				"failure reading local klient /kite response: %s", err,
			)}
		}

		if bytes.Compare(kiteHTTPResponse, bytes.TrimSpace(p)) != 0 {
			return ErrHealthUnexpectedResponse{Message: fmt.Sprintf(
				"local klient /kite route is returning an unexpected response: %s", p,
			)}
		}
	}

	// The only error CreateKlientClient returns (currently) is kite read
	// error, so we can handle that.
	k, err := klient.CreateKlientWithDefaultOpts()
	if err != nil {
		return ErrHealthUnreadableKiteKey{Message: fmt.Sprintf(
			"klient kite key is unable to be read: %s", err,
		)}
	}

	// TODO: Identify varing Dial errors to produce meaningful health
	// responses.
	if err = k.Dial(); err != nil {
		return ErrHealthDialFailed{Message: fmt.Sprintf(
			"dailing local klient failed: %s", err,
		)}
	}

	return nil
}
Example #3
0
// NewRunCommand is the required initializer for RunCommand.
func NewRunCommand() (*RunCommand, error) {
	klientKite, err := klient.CreateKlientWithDefaultOpts()
	if err != nil {
		return nil, err
	}

	if err := klientKite.Dial(); err != nil {
		return nil, err
	}

	return &RunCommand{Transport: klientKite}, nil
}
Example #4
0
// ListCommand returns list of remote machines belonging to user or that can be
// accessed by the user.
func ListCommand(c *cli.Context, log logging.Logger, _ string) int {
	if len(c.Args()) != 0 {
		cli.ShowCommandHelp(c, "list")
		return 1
	}

	showAll := c.Bool("all")

	k, err := klient.CreateKlientWithDefaultOpts()
	if err != nil {
		log.Error("Error creating klient client. err:%s", err)
		fmt.Println(defaultHealthChecker.CheckAllFailureOrMessagef(GenericInternalError))
		return 1
	}

	if err := k.Dial(); err != nil {
		log.Error("Error dialing klient client. err:%s", err)
		fmt.Println(defaultHealthChecker.CheckAllFailureOrMessagef(GenericInternalError))
		return 1
	}

	infos, err := getListOfMachines(k)
	if err != nil {
		log.Error("Error listing machines. err:%s", err)
		fmt.Println(getListErrRes(err, defaultHealthChecker))
		return 1
	}

	// Sort our infos
	sort.Sort(infos)

	// Filter out infos for listing and json.
	for i := 0; i < len(infos); i++ {
		info := &infos[i]

		onlineRecently := time.Since(info.OnlineAt) <= 24*time.Hour
		hasMounts := len(info.Mounts) > 0
		// Do not show machines that have been offline for more than 24h,
		// but only if the machine doesn't have any mounts and we aren't using the --all
		// flag.
		if !hasMounts && !showAll && !onlineRecently {
			// Remove this element from the slice, because we're not showing it as
			// described above.
			infos = append(infos[:i], infos[i+1:]...)
			// Decrement the index, since we're removing the item from the slice.
			i--
			continue
		}

		// For a more clear UX, replace the team name of the default Koding team,
		// with Koding.com
		for i, team := range info.Teams {
			if team == "Koding" {
				info.Teams[i] = "koding.com"
			}
		}

		switch info.MachineStatus {
		case machine.MachineOffline:
			info.MachineStatusName = "offline"
		case machine.MachineOnline:
			info.MachineStatusName = "online"
		case machine.MachineDisconnected:
			info.MachineStatusName = "disconnected"
		case machine.MachineConnected:
			info.MachineStatusName = "connected"
		case machine.MachineError:
			info.MachineStatusName = "error"
		case machine.MachineRemounting:
			info.MachineStatusName = "remounting"
		default:
			info.MachineStatusName = "unknown"
		}
	}

	if c.Bool("json") {
		jsonBytes, err := json.MarshalIndent(infos, "", "  ")
		if err != nil {
			log.Error("Marshalling infos to json failed. err:%s", err)
			fmt.Println(GenericInternalError)
			return 1
		}

		fmt.Println(string(jsonBytes))
		return 0
	}

	w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0)
	fmt.Fprintf(w, "\tTEAM\tLABEL\tIP\tALIAS\tSTATUS\tMOUNTED PATHS\n")
	for i, info := range infos {
		// Join multiple teams into a single identifier
		team := strings.Join(info.Teams, ",")

		var formattedMount string
		if len(info.Mounts) > 0 {
			formattedMount += fmt.Sprintf(
				"%s -> %s",
				shortenPath(info.Mounts[0].LocalPath),
				shortenPath(info.Mounts[0].RemotePath),
			)
		}

		// Currently we are displaying the status message over the formattedMount,
		// if it exists.
		if info.StatusMessage != "" {
			formattedMount = info.StatusMessage
		}

		fmt.Fprintf(w, "  %d.\t%s\t%s\t%s\t%s\t%s\t%s\n",
			i+1, team, info.MachineLabel, info.IP, info.VMName, info.MachineStatusName,
			formattedMount,
		)
	}
	w.Flush()

	return 0
}
Example #5
0
// List retrieves user's machines from kloud.
func List(options *ListOptions) ([]*Info, error) {
	var (
		listReq = stack.MachineListRequest{}
		listRes = stack.MachineListResponse{}
	)

	// Get info from kloud.
	if err := kloud.Call("machine.list", &listReq, &listRes); err != nil {
		return nil, err
	}

	// Register machines to klient and get aliases.
	//
	// TODO(ppknap): this is copied from klientctl old list and will be reworked.
	k, err := klient.CreateKlientWithDefaultOpts()
	if err != nil {
		fmt.Fprintln(os.Stderr, "Error creating klient:", err)
		return nil, err
	}

	if err := k.Dial(); err != nil {
		fmt.Fprintln(os.Stderr, "Error dialing klient:", err)
		return nil, err
	}

	createReq := machinegroup.CreateRequest{
		Addresses: make(map[kmachine.ID][]kmachine.Addr),
	}
	for _, m := range listRes.Machines {
		createReq.Addresses[kmachine.ID(m.ID)] = []kmachine.Addr{
			{
				Network:   "ip",
				Value:     m.IP,
				UpdatedAt: time.Now(),
			},
			{
				Network:   "kite",
				Value:     m.QueryString,
				UpdatedAt: time.Now(),
			},
			{
				Network:   "http",
				Value:     m.RegisterURL,
				UpdatedAt: time.Now(),
			},
		}
	}
	createRaw, err := k.Tell("machine.create", createReq)
	if err != nil {
		return nil, err
	}

	createRes := machinegroup.CreateResponse{}
	if err := createRaw.Unmarshal(&createRes); err != nil {
		return nil, err
	}

	infos := make([]*Info, len(listRes.Machines))
	for i, m := range listRes.Machines {
		infos[i] = &Info{
			ID:          m.ID,
			Alias:       createRes.Aliases[kmachine.ID(m.ID)],
			Team:        m.Team,
			Stack:       m.Stack,
			Provider:    m.Provider,
			Label:       m.Label,
			IP:          m.IP,
			QueryString: m.QueryString,
			RegisterURL: m.RegisterURL,
			CreatedAt:   m.CreatedAt,
			Status: kmachine.MergeStatus(kmachine.Status{
				State:  fromMachineStateString(m.Status.State),
				Reason: m.Status.Reason,
				Since:  m.Status.ModifiedAt,
			}, createRes.Statuses[kmachine.ID(m.ID)]),
			Username: machineUserFromUsers(m.Users),
			Owner:    ownerFromUsers(m.Users),
		}
	}

	// Sort items before we return.
	sort.Sort(InfoSlice(infos))

	return infos, nil
}
Example #6
0
// SSH connects to remote machine using SSH protocol.
func SSH(options *SSHOptions) error {
	// Translate identifier to machine ID.
	//
	// TODO(ppknap): this is copied from klientctl old list and will be reworked.
	k, err := klient.CreateKlientWithDefaultOpts()
	if err != nil {
		fmt.Fprintln(os.Stderr, "Error creating klient:", err)
		return err
	}

	if err := k.Dial(); err != nil {
		fmt.Fprintln(os.Stderr, "Error dialing klient:", err)
		return err
	}

	idReq := machinegroup.IDRequest{
		Identifier: options.Identifier,
	}

	idRaw, err := k.Tell("machine.id", idReq)
	if err != nil {
		return err
	}

	idRes := machinegroup.IDResponse{}
	if err := idRaw.Unmarshal(&idRes); err != nil {
		return err
	}

	// Get local public key in case we need to copy it to remote machine.
	path, err := ssh.GetKeyPath(nil)
	if err != nil {
		return err
	}

	pubPath, privPath, err := ssh.KeyPaths(path)
	if err != nil {
		return err
	}

	pubkey, err := ssh.PublicKey(pubPath)
	if err != nil && err != ssh.ErrPublicKeyNotFound {
		return err
	}

	// Generate new key pair if it does not exist.
	if err == ssh.ErrPublicKeyNotFound {
		if pubkey, _, err = ssh.GenerateSaved(pubPath, privPath); err != nil {
			return err
		}
	}

	// Add created key to authorized hosts on remote machine.
	sshReq := machinegroup.SSHRequest{
		ID:        idRes.ID,
		Username:  options.Username,
		PublicKey: pubkey,
	}

	sshRaw, err := k.Tell("machine.ssh", sshReq)
	if err != nil {
		return err
	}

	sshRes := machinegroup.SSHResponse{}
	if err := sshRaw.Unmarshal(&sshRes); err != nil {
		return err
	}

	// TODO(ppknap): move this to ssh package.
	args := []string{
		"-i", privPath,
		"-o", "StrictHostKeychecking=no",
		"-o", "UserKnownHostsFile=/dev/null",
		"-o", "ServerAliveInterval=300",
		"-o", "ServerAliveCountMax=3",
		"-o", "ConnectTimeout=7",
		"-o", "ConnectionAttempts=1",
		sshRes.Username + "@" + sshRes.Host,
	}

	if sshRes.Port > 0 {
		args = append(args, "-p", strconv.Itoa(sshRes.Port))
	}

	options.Log.Info("Executing command: ssh %s", strings.Join(args, " "))
	cmd := exec.Command("ssh", args...)
	cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
	return cmd.Run()
}