Skip to content

Commit

Permalink
Implement completion for "kops create keypair"
Browse files Browse the repository at this point in the history
  • Loading branch information
johngmyers committed Jun 29, 2021
1 parent 1bed90a commit 98b6bf8
Show file tree
Hide file tree
Showing 27 changed files with 146 additions and 67 deletions.
122 changes: 101 additions & 21 deletions cmd/kops/create_keypair.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ import (
"time"

"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/klog/v2"
"k8s.io/kops/pkg/commands/commandutils"

"k8s.io/kops/cmd/kops/util"
"k8s.io/kops/pkg/pki"
Expand All @@ -39,6 +41,21 @@ import (
var (
createKeypairLong = templates.LongDesc(i18n.T(`
Add a CA certificate and private key to a keyset.
If neither a certificate nor a private key is provided, a new self-signed
certificate and private key will be generated.
If no certificate is provided but a private key is, a self-signed
certificate will be generated from the provided private key.
If a certificate is provided but no private key is, the certificate
will be added to the keyset without a private key. Such a certificate
cannot be made primary.
One of the certificate/private key pairs in each keyset must be primary.
The primary keypair is the one used to issue certificates (or, for the
"service-account" keyset, service-account tokens). As a consequence, the
first entry in a keyset must be made primary.
`))

createKeypairExample = templates.Examples(i18n.T(`
Expand All @@ -59,43 +76,45 @@ type CreateKeypairOptions struct {
Primary bool
}

var keysetCommonNames = map[string]string{
"ca": "kubernetes",
"etcd-clients-ca-cilium": "etcd-clients-ca-cilium",
"service-account": "service-account",
}
var rotatableKeysets = sets.NewString(
"ca",
"etcd-clients-ca-cilium",
"service-account",
)

// NewCmdCreateKeypair returns a create keypair command.
func NewCmdCreateKeypair(f *util.Factory, out io.Writer) *cobra.Command {
options := &CreateKeypairOptions{}

cmd := &cobra.Command{
Use: "keypair KEYSET",
Use: "keypair keyset",
Short: createKeypairShort,
Long: createKeypairLong,
Example: createKeypairExample,
Run: func(cmd *cobra.Command, args []string) {
ctx := context.TODO()

options.ClusterName = rootCommand.ClusterName()
Args: func(cmd *cobra.Command, args []string) error {
options.ClusterName = rootCommand.ClusterName(true)

if options.ClusterName == "" {
exitWithError(fmt.Errorf("--name is required"))
return
return fmt.Errorf("--name is required")
}

if len(args) == 0 {
exitWithError(fmt.Errorf("must specify name of keyset to add keypair to"))
}
if len(args) != 1 {
exitWithError(fmt.Errorf("can only add to one keyset at a time"))
return fmt.Errorf("must specify name of keyset to add keypair to")
}

options.Keyset = args[0]

err := RunCreateKeypair(ctx, f, out, options)
if err != nil {
exitWithError(err)
if len(args) != 1 {
return fmt.Errorf("can only add to one keyset at a time")
}

return nil
},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return completeCreateKeyset(options, args, toComplete)
},
RunE: func(cmd *cobra.Command, args []string) error {
return RunCreateKeypair(context.TODO(), f, out, options)
},
}

Expand All @@ -108,8 +127,7 @@ func NewCmdCreateKeypair(f *util.Factory, out io.Writer) *cobra.Command {

// RunCreateKeypair adds a custom CA certificate and private key.
func RunCreateKeypair(ctx context.Context, f *util.Factory, out io.Writer, options *CreateKeypairOptions) error {
commonName := keysetCommonNames[options.Keyset]
if commonName == "" {
if !rotatableKeysets.Has(options.Keyset) {
return fmt.Errorf("adding keypair to %q is not supported", options.Keyset)
}

Expand Down Expand Up @@ -151,6 +169,10 @@ func RunCreateKeypair(ctx context.Context, f *util.Factory, out io.Writer, optio
}
}

commonName := options.Keyset
if commonName == "ca" {
commonName = "kubernetes"
}
req := pki.IssueCertRequest{
Type: "ca",
Subject: pkix.Name{CommonName: "cn=" + commonName},
Expand Down Expand Up @@ -203,3 +225,61 @@ func RunCreateKeypair(ctx context.Context, f *util.Factory, out io.Writer, optio
}
return nil
}

func completeCreateKeyset(options *CreateKeypairOptions, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
commandutils.ConfigureKlogForCompletion()

options.ClusterName = rootCommand.ClusterName(false)

if options.ClusterName == "" {
return []string{"--name"}, cobra.ShellCompDirectiveNoFileComp
}

ctx := context.TODO()
cluster, err := GetCluster(ctx, &rootCommand, options.ClusterName)
if err != nil {
return commandutils.CompletionError("getting cluster", err)
}

clientSet, err := rootCommand.Clientset()
if err != nil {
return commandutils.CompletionError("getting clientset", err)
}

keyStore, err := clientSet.KeyStore(cluster)
if err != nil {
return commandutils.CompletionError("getting keystore", err)
}

if len(args) == 0 {
list, err := keyStore.ListKeysets()
if err != nil {
return commandutils.CompletionError("listing keysets", err)
}

var keysets []string
for name := range list {
if rotatableKeysets.Has(name) {
keysets = append(keysets, name)
}
}

return keysets, cobra.ShellCompDirectiveNoFileComp
}

if len(args) > 1 {
return commandutils.CompletionError("too many arguments", nil)
}

var flags []string
if options.CertPath == "" {
flags = append(flags, "--cert")
}
if options.PrivateKeyPath == "" {
flags = append(flags, "--key")
}
if !options.Primary && (options.CertPath == "" || options.PrivateKeyPath != "") {
flags = append(flags, "--primary")
}
return flags, cobra.ShellCompDirectiveNoFileComp
}
2 changes: 1 addition & 1 deletion cmd/kops/create_secret_cilium_encryptionconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func NewCmdCreateSecretCiliumEncryptionConfig(f *util.Factory, out io.Writer) *c
exitWithError(err)
}

options.ClusterName = rootCommand.ClusterName()
options.ClusterName = rootCommand.ClusterName(true)

err = RunCreateSecretCiliumEncryptionConfig(ctx, f, os.Stdout, options)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/kops/create_secret_dockerconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func NewCmdCreateSecretDockerConfig(f *util.Factory, out io.Writer) *cobra.Comma
exitWithError(err)
}

options.ClusterName = rootCommand.ClusterName()
options.ClusterName = rootCommand.ClusterName(true)

err = RunCreateSecretDockerConfig(ctx, f, os.Stdout, options)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/kops/create_secret_encryptionconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func NewCmdCreateSecretEncryptionConfig(f *util.Factory, out io.Writer) *cobra.C
exitWithError(err)
}

options.ClusterName = rootCommand.ClusterName()
options.ClusterName = rootCommand.ClusterName(true)

err = RunCreateSecretEncryptionConfig(ctx, f, os.Stdout, options)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/kops/create_secret_sshpublickey.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func NewCmdCreateSecretPublicKey(f *util.Factory, out io.Writer) *cobra.Command
exitWithError(err)
}

options.ClusterName = rootCommand.ClusterName()
options.ClusterName = rootCommand.ClusterName(true)

err = RunCreateSecretPublicKey(ctx, f, os.Stdout, options)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/kops/create_secret_weave_encryptionconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func NewCmdCreateSecretWeaveEncryptionConfig(f *util.Factory, out io.Writer) *co
exitWithError(err)
}

options.ClusterName = rootCommand.ClusterName()
options.ClusterName = rootCommand.ClusterName(true)

err = RunCreateSecretWeaveEncryptionConfig(ctx, f, options)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/kops/delete_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func NewCmdDeleteInstance(f *util.Factory, out io.Writer) *cobra.Command {
cmd.Run = func(cmd *cobra.Command, args []string) {
ctx := context.TODO()

clusterName := rootCommand.ClusterName()
clusterName := rootCommand.ClusterName(true)

if clusterName == "" {
exitWithError(fmt.Errorf("--name is required"))
Expand Down
2 changes: 1 addition & 1 deletion cmd/kops/delete_instancegroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ func NewCmdDeleteInstanceGroup(f *util.Factory, out io.Writer) *cobra.Command {
groupName := args[0]
options.GroupName = groupName

options.ClusterName = rootCommand.ClusterName()
options.ClusterName = rootCommand.ClusterName(true)

if !options.Yes {
message := fmt.Sprintf("Do you really want to delete instance group %q? This action cannot be undone.", groupName)
Expand Down
2 changes: 1 addition & 1 deletion cmd/kops/delete_secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func NewCmdDeleteSecret(f *util.Factory, out io.Writer) *cobra.Command {
options.SecretID = args[2]
}

options.ClusterName = rootCommand.ClusterName()
options.ClusterName = rootCommand.ClusterName(true)

err := RunDeleteSecret(ctx, f, out, options)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/kops/distrust_keypair.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func NewCmdDistrustKeypair(f *util.Factory, out io.Writer) *cobra.Command {
Run: func(cmd *cobra.Command, args []string) {
ctx := context.TODO()

options.ClusterName = rootCommand.ClusterName()
options.ClusterName = rootCommand.ClusterName(true)
if options.ClusterName == "" {
exitWithError(fmt.Errorf("--name is required"))
return
Expand Down
2 changes: 1 addition & 1 deletion cmd/kops/get_assets.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func NewCmdGetAssets(f *util.Factory, out io.Writer, getOptions *GetOptions) *co

func RunGetAssets(ctx context.Context, f *util.Factory, out io.Writer, options *GetAssetsOptions) error {

clusterName := rootCommand.ClusterName()
clusterName := rootCommand.ClusterName(true)
options.clusterName = clusterName
if clusterName == "" {
return fmt.Errorf("--name is required")
Expand Down
2 changes: 1 addition & 1 deletion cmd/kops/get_instancegroups.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func NewCmdGetInstanceGroups(f *util.Factory, out io.Writer, getOptions *GetOpti
func RunGetInstanceGroups(ctx context.Context, options *GetInstanceGroupsOptions, args []string) error {
out := os.Stdout

clusterName := rootCommand.ClusterName()
clusterName := rootCommand.ClusterName(true)
if clusterName == "" {
return fmt.Errorf("--name is required")
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/kops/get_instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func RunGetInstances(ctx context.Context, f *util.Factory, out io.Writer, option
return err
}

clusterName := rootCommand.ClusterName()
clusterName := rootCommand.ClusterName(true)
options.clusterName = clusterName
if clusterName == "" {
return fmt.Errorf("--name is required")
Expand Down
4 changes: 2 additions & 2 deletions cmd/kops/promote_keypair.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func NewCmdPromoteKeypair(f *util.Factory, out io.Writer) *cobra.Command {
Run: func(cmd *cobra.Command, args []string) {
ctx := context.TODO()

options.ClusterName = rootCommand.ClusterName()
options.ClusterName = rootCommand.ClusterName(true)

if options.ClusterName == "" {
exitWithError(fmt.Errorf("--name is required"))
Expand Down Expand Up @@ -95,7 +95,7 @@ func NewCmdPromoteKeypair(f *util.Factory, out io.Writer) *cobra.Command {

// RunPromoteKeypair promotes a keypair.
func RunPromoteKeypair(ctx context.Context, f *util.Factory, out io.Writer, options *PromoteKeypairOptions) error {
if keysetCommonNames[options.Keyset] == "" {
if !rotatableKeysets.Has(options.Keyset) {
return fmt.Errorf("promoting keypairs for %q is not supported", options.Keyset)
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/kops/rollingupdatecluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ func NewCmdRollingUpdateCluster(f *util.Factory, out io.Writer) *cobra.Command {
return
}

clusterName := rootCommand.ClusterName()
clusterName := rootCommand.ClusterName(true)
if clusterName == "" {
exitWithError(fmt.Errorf("--name is required"))
return
Expand Down
28 changes: 7 additions & 21 deletions cmd/kops/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,22 +235,14 @@ func (c *RootCmd) ProcessArgs(args []string) error {
return fmt.Errorf("expected a single <clustername> to be passed as an argument")
}

func (c *RootCmd) ClusterName() string {
func (c *RootCmd) ClusterName(verbose bool) string {
if c.clusterName != "" {
return c.clusterName
}

c.clusterName = ClusterNameFromKubecfg()

return c.clusterName
}

func ClusterNameFromKubecfg() string {
// Read from kubeconfig
pathOptions := clientcmd.NewDefaultPathOptions()

clusterName := ""

config, err := pathOptions.GetStartingConfig()
if err != nil {
klog.Warningf("error reading kubecfg: %v", err)
Expand All @@ -263,28 +255,22 @@ func ClusterNameFromKubecfg() string {
} else if context.Cluster == "" {
klog.Warningf("context %q in kubecfg did not have a cluster", config.CurrentContext)
} else {
fmt.Fprintf(os.Stderr, "Using cluster from kubectl context: %s\n\n", context.Cluster)
clusterName = context.Cluster
if verbose {
fmt.Fprintf(os.Stderr, "Using cluster from kubectl context: %s\n\n", context.Cluster)
}
c.clusterName = context.Cluster
}
}

//config, err := readKubectlClusterConfig()
//if err != nil {
// klog.Warningf("error reading kubecfg: %v", err)
//} else if config != nil && config.Name != "" {
// fmt.Fprintf(os.Stderr, "Using cluster from kubectl context: %s\n\n", config.Name)
// c.clusterName = config.Name
//}

return clusterName
return c.clusterName
}

func (c *RootCmd) Clientset() (simple.Clientset, error) {
return c.factory.Clientset()
}

func (c *RootCmd) Cluster(ctx context.Context) (*kopsapi.Cluster, error) {
clusterName := c.ClusterName()
clusterName := c.ClusterName(true)
if clusterName == "" {
return nil, fmt.Errorf("--name is required")
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/kops/set_cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func NewCmdSetCluster(f *util.Factory, out io.Writer) *cobra.Command {
}

if options.ClusterName == "" {
options.ClusterName = rootCommand.ClusterName()
options.ClusterName = rootCommand.ClusterName(true)
}

if err := commands.RunSetCluster(ctx, f, cmd, out, options); err != nil {
Expand Down
2 changes: 1 addition & 1 deletion cmd/kops/set_instancegroups.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func NewCmdSetInstancegroup(f *util.Factory, out io.Writer) *cobra.Command {
}
}

options.ClusterName = rootCommand.ClusterName()
options.ClusterName = rootCommand.ClusterName(true)

if err := commands.RunSetInstancegroup(ctx, f, cmd, out, options); err != nil {
exitWithError(err)
Expand Down
Loading

0 comments on commit 98b6bf8

Please sign in to comment.