xetys/hetzner-kube

View on GitHub
cmd/ssh_key_add.go

Summary

Maintainability
A
45 mins
Test Coverage
package cmd

import (
    "bytes"
    "errors"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "os"
    "strings"

    "github.com/hetznercloud/hcloud-go/hcloud"
    "github.com/mitchellh/go-homedir"
    "github.com/spf13/cobra"
    "github.com/xetys/hetzner-kube/pkg/clustermanager"
    "golang.org/x/crypto/ssh"
)

// sshKeyAddCmd represents the sshKeyAdd command
var sshKeyAddCmd = &cobra.Command{
    Use:   "add",
    Short: "adds a new SSH key to the Hetzner Cloud project and local configuration",
    Long: `This sub-command saves the path of the provided SSH private key in a configuration file on your local machine.
Then it uploads it corresponding public key with the provided name to the Hetzner Cloud project, associated by the current context.

Note: the private key is never uploaded to any server at any time.`,
    PreRunE: validateFlags,
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("sshKeyAdd called")
        name, _ := cmd.Flags().GetString("name")
        publicKeyPath, _ := cmd.Flags().GetString("public-key-path")
        privateKeyPath, _ := cmd.Flags().GetString("private-key-path")

        // Find home directory.
        home, err := homedir.Dir()
        if err != nil {
            fmt.Println(err)
            os.Exit(1)
        }

        privateKeyPath = strings.Replace(privateKeyPath, "~", home, 1)
        publicKeyPath = strings.Replace(publicKeyPath, "~", home, 1)

        var (
            data []byte
        )
        if publicKeyPath == "-" {
            data, err = ioutil.ReadAll(os.Stdin)
        } else {
            data, err = ioutil.ReadFile(publicKeyPath)
        }
        if err != nil {
            log.Fatalln(err)
        }
        publicKey := string(data)

        opts := hcloud.SSHKeyCreateOpts{
            Name:      name,
            PublicKey: publicKey,
        }

        context := AppConf.Context
        client := AppConf.Client
        sshKey, res, err := client.SSHKey.Create(context, opts)

        if res.StatusCode == http.StatusConflict {
            pkey, _, _, _, err := ssh.ParseAuthorizedKey(data)
            if err != nil {
                log.Fatalln(err)
            }
            // check if the key is already in to local app config
            for _, sshKey := range AppConf.Config.SSHKeys {
                localData, err := ioutil.ReadFile(sshKey.PublicKeyPath)
                if err != nil {
                    log.Fatalln(err)
                }
                localPkey, _, _, _, err := ssh.ParseAuthorizedKey(localData)
                if err != nil {
                    log.Fatalln(err)
                }
                // if the key is in the local app config print a message and return
                if bytes.Equal(pkey.Marshal(), localPkey.Marshal()) {
                    fmt.Printf("SSH key does already exist in your config as %s\n", sshKey.Name)
                    return
                }
            }
            // if the key is not in the local app config, fetch it from hetzner
            sshKeys, err := client.SSHKey.All(context)
            if err != nil {
                log.Fatalln(err)
            }
            for _, sshKeyHetzner := range sshKeys {
                hetznerPkey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(sshKeyHetzner.PublicKey))
                if err != nil {
                    log.Fatalln(err)
                }
                if bytes.Equal(pkey.Marshal(), hetznerPkey.Marshal()) {
                    fmt.Printf("SSH key does already exist on hetzner as '%s'\n", sshKeyHetzner.Name)
                    fmt.Printf("SSH key will be added to your config as '%s'\n", sshKeyHetzner.Name)
                    // We replace the failed request response with the fetched sshkey that has the same public key
                    sshKey = sshKeyHetzner
                    break
                }
                if sshKeyHetzner.Name == name {
                    log.Fatalf("Name '%s' is already taken!", name)
                }
            }
        } else if err != nil {
            log.Fatalln(err)
        }

        AppConf.Config.AddSSHKey(clustermanager.SSHKey{
            Name:           sshKey.Name,
            PrivateKeyPath: privateKeyPath,
            PublicKeyPath:  publicKeyPath,
        })

        AppConf.Config.WriteCurrentConfig()

        fmt.Printf("SSH key %s(%d) created\n", sshKey.Name, sshKey.ID)
    },
}

func validateFlags(cmd *cobra.Command, args []string) error {
    if err := AppConf.assertActiveContext(); err != nil {
        return err
    }

    if name, _ := cmd.Flags().GetString("name"); name == "" {
        return errors.New("flag --name is required")
    }

    privateKeyPath, _ := cmd.Flags().GetString("private-key-path")
    if privateKeyPath == "" {
        return errors.New("flag --private-key-path cannot be empty")
    }

    publicKeyPath, _ := cmd.Flags().GetString("public-key-path")
    if publicKeyPath == "" {
        return errors.New("flag --public-key-path cannot be empty")
    }

    // Find home directory.
    home, err := homedir.Dir()
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    privateKeyPath = strings.Replace(privateKeyPath, "~", home, 1)
    publicKeyPath = strings.Replace(publicKeyPath, "~", home, 1)
    if _, err := os.Stat(privateKeyPath); os.IsNotExist(err) {
        return fmt.Errorf("could not find private key '%s'", privateKeyPath)

    }

    if _, err := os.Stat(publicKeyPath); os.IsNotExist(err) {
        return fmt.Errorf("could not find public key '%s'", publicKeyPath)
    }

    return nil
}

func init() {
    sshKeyCmd.AddCommand(sshKeyAddCmd)

    sshKeyAddCmd.Flags().StringP("name", "n", "", "the name of the key")
    sshKeyAddCmd.Flags().String("private-key-path", "~/.ssh/id_rsa", "the path to the private key")
    sshKeyAddCmd.Flags().String("public-key-path", "~/.ssh/id_rsa.pub", "the path to the public key")
}