-
Notifications
You must be signed in to change notification settings - Fork 112
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: kubectl plugin first iteration
Signed-off-by: Dario Tranchitella <dario@tranchitella.eu>
- Loading branch information
1 parent
9da801b
commit 63dcacd
Showing
14 changed files
with
743 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// Copyright 2022 Clastix Labs | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package certificates | ||
|
||
import ( | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
func NewCertificatesGroup() *cobra.Command { | ||
return &cobra.Command{ | ||
Use: "certificates", | ||
Short: "Certificate operations", | ||
Long: "Performs operations on Tenant Control Plance related certificates.", | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
// Copyright 2022 Clastix Labs | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package certificates | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"strings" | ||
|
||
"github.com/spf13/cobra" | ||
"k8s.io/apimachinery/pkg/util/sets" | ||
"k8s.io/cli-runtime/pkg/genericclioptions" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
|
||
"github.com/clastix/kamaji/cmd/kubectl-kamaji/utils" | ||
"github.com/clastix/kamaji/pkg/cli" | ||
) | ||
|
||
func NewRotateCertificates(writer io.Writer, flags *genericclioptions.ConfigFlags, k8sClient client.Client) *cobra.Command { | ||
var all bool | ||
var certificates []string | ||
|
||
cmd := cobra.Command{ | ||
Use: "rotate {TCP_NAME} { --all | --certificates={CERTS_LIST} }", | ||
Example: " kubectl [--namespace=$NAMESPACE] kamaji certificates rotate $TCP_NAME --all=true\n" + | ||
" kubectl [--namespace=$NAMESPACE] kamaji certificates rotate $TCP_NAME --certificates=APIServer\n" + | ||
" kubectl [--namespace=$NAMESPACE] kamaji certificates rotate $TCP_NAME --certificates=FrontProxyCA,FrontProxyClient", | ||
Short: "Get kubeconfig", | ||
Long: "Rotate one ore more Tenant Control Plane certificates.\n" + | ||
"\n" + | ||
"The CLI flag --certificates allows to specify which certificates kind should be rotated, such as: " + | ||
strings.Join(cli.RotateCertificatesMap.Keys(), ", ") + ".\n" + | ||
"At least one must be specified, or mutually exclusive with --all", | ||
Args: cobra.ExactArgs(1), | ||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { | ||
return utils.ValidArgsFunction(k8sClient, *flags.Namespace)(cmd, args, toComplete) | ||
}, | ||
SilenceUsage: true, | ||
PreRunE: func(cmd *cobra.Command, args []string) error { | ||
switch { | ||
case all: | ||
return nil | ||
case len(certificates) == 0: | ||
return fmt.Errorf("at least one certificate must be specified") | ||
default: | ||
for _, arg := range certificates { | ||
if _, ok := cli.RotateCertificatesMap[arg]; !ok { | ||
return fmt.Errorf("unrecognized certificate, %q", arg) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
}, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
certSet := sets.New[string](certificates...) | ||
|
||
certsToDelete := make(cli.RotateCertOptions) | ||
for k, v := range cli.RotateCertificatesMap { | ||
if all || certSet.Has(k) { | ||
certsToDelete[k] = v | ||
} | ||
} | ||
|
||
if err := (&cli.Helper{Client: k8sClient}).RotateCertificate(cmd.Context(), *flags.Namespace, args[0], certsToDelete); err != nil { | ||
return err | ||
} | ||
|
||
_, _ = writer.Write([]byte(fmt.Sprintf("The following certificates have been successfully rotated: %s", strings.Join(certsToDelete.Keys(), ",")))) | ||
|
||
return nil | ||
}, | ||
} | ||
|
||
cmd.Flags().StringSliceVar(&certificates, "certificates", []string{}, "Specify which certificates should be rotated, at least one should be provided.") | ||
cmd.Flags().BoolVar(&all, "all", false, "When specified, rotate all available certificates related to this Tenant Control Plane.") | ||
|
||
return &cmd | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
// Copyright 2022 Clastix Labs | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package kubeconfig | ||
|
||
import ( | ||
"io" | ||
|
||
"github.com/spf13/cobra" | ||
"k8s.io/cli-runtime/pkg/genericclioptions" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
|
||
"github.com/clastix/kamaji/cmd/kubectl-kamaji/utils" | ||
"github.com/clastix/kamaji/pkg/cli" | ||
) | ||
|
||
func NewGetKubeconfig(writer io.Writer, flags *genericclioptions.ConfigFlags, k8sClient client.Client) *cobra.Command { | ||
var secretKey string | ||
|
||
cmd := cobra.Command{ | ||
Use: "get {TCP_NAME}", | ||
Example: " kubectl [--namespace=$NAMESPACE] kamaji kubeconfig get $TCP_NAME [--secret-type=$KEY]", | ||
Short: "Get kubeconfig", | ||
Long: "Retrieve the decoded content of a Tenant Control Plane kubeconfig.\n" + | ||
"\n" + | ||
"The CLI flag --secret-type refers to the key name of the kubeconfig you want to extract. " + | ||
"By default, the command will extract the `admin.conf` one, you can specify your preferred one until it exists.", | ||
Args: cobra.ExactArgs(1), | ||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { | ||
return utils.ValidArgsFunction(k8sClient, *flags.Namespace)(cmd, args, toComplete) | ||
}, | ||
SilenceUsage: true, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
_, data, kcErr := (&cli.Helper{Client: k8sClient}).GetKubeconfig(cmd.Context(), *flags.Namespace, args[0], secretKey) | ||
if kcErr != nil { | ||
return kcErr | ||
} | ||
|
||
_, _ = writer.Write(data) | ||
|
||
return nil | ||
}, | ||
} | ||
|
||
cmd.Flags().StringVar(&secretKey, "secret-key", "admin.conf", "The Secret key used to retrieve the kubeconfig.") | ||
|
||
return &cmd | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// Copyright 2022 Clastix Labs | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package kubeconfig | ||
|
||
import ( | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
func NewKubeconfigGroup() *cobra.Command { | ||
return &cobra.Command{ | ||
Use: "kubeconfig", | ||
Short: "kubeconfig operations", | ||
Long: "Performs operations on kubeconfig objects related to the given Tenant Control Plane.", | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
// Copyright 2022 Clastix Labs | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package kubeconfig | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"strings" | ||
|
||
"github.com/spf13/cobra" | ||
"k8s.io/apimachinery/pkg/util/sets" | ||
"k8s.io/cli-runtime/pkg/genericclioptions" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
|
||
"github.com/clastix/kamaji/cmd/kubectl-kamaji/utils" | ||
"github.com/clastix/kamaji/pkg/cli" | ||
) | ||
|
||
func NewRotateKubeconfig(writer io.Writer, flags *genericclioptions.ConfigFlags, k8sClient client.Client) *cobra.Command { | ||
var components []string | ||
var all bool | ||
|
||
cmd := cobra.Command{ | ||
Use: "rotate {TCP_NAME} { --all=true | --components={COMPONENTS_LIST} }", | ||
Example: " kubectl [--namespace=$NAMESPACE] kamaji kubeconfig rotate $TCP_NAME --all=true\n" + | ||
" kubectl [--namespace=$NAMESPACE] kamaji kubeconfig rotate $TCP_NAME --components=Admin\n" + | ||
" kubectl [--namespace=$NAMESPACE] kamaji kubeconfig rotate $TCP_NAME --certificates=Admin,ControllerManager", | ||
Short: "Rotate a kubeconfig", | ||
Long: "Kubeconfig generated by Kamaji are based on client certificate authentication. " + | ||
"Despite Kamaji performs rotation thanks to the CertificateLifecycle following the defined deadline, " + | ||
"you can trigger this action manually according to your needs.\n" + | ||
"\n" + | ||
"The CLI flag --components allows to specify which Control Plane components' kubeconfig objects should be rotated, " + | ||
"possible values: " + strings.Join(cli.RotateKubeconfigMap.Keys(), ", ") + ".\n" + | ||
"At least one must be specified, or mutually exclusive with --all.", | ||
Args: cobra.ExactArgs(1), | ||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { | ||
return utils.ValidArgsFunction(k8sClient, *flags.Namespace)(cmd, args, toComplete) | ||
}, | ||
SilenceUsage: true, | ||
PreRunE: func(cmd *cobra.Command, args []string) error { | ||
switch { | ||
case all: | ||
return nil | ||
case len(components) == 0: | ||
return fmt.Errorf("at least one component must be specified") | ||
default: | ||
for _, arg := range components { | ||
if _, ok := cli.RotateKubeconfigMap[arg]; !ok { | ||
return fmt.Errorf("unrecognized component, %q", arg) | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
}, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
componentSet := sets.New[string](components...) | ||
|
||
toDelete := make(cli.RotateKubeconfigOptions) | ||
for k, v := range cli.RotateKubeconfigMap { | ||
if all || componentSet.Has(k) { | ||
toDelete[k] = v | ||
} | ||
} | ||
|
||
if err := (&cli.Helper{Client: k8sClient}).RotateKubeconfig(cmd.Context(), *flags.Namespace, args[0], toDelete); err != nil { | ||
return err | ||
} | ||
|
||
_, _ = writer.Write([]byte(fmt.Sprintf("The following kubeconfig resources have been successfully rotated: %s", strings.Join(toDelete.Keys(), ",")))) | ||
|
||
return nil | ||
}, | ||
} | ||
|
||
cmd.Flags().StringSliceVar(&components, "components", []string{}, "List of Control Plane components that should have kubeconfig rotated.") | ||
cmd.Flags().BoolVar(&all, "all", false, "When specified, rotate all components' kubeconfig resources related to this Tenant Control Plane.") | ||
|
||
return &cmd | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
// Copyright 2022 Clastix Labs | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package main | ||
|
||
import ( | ||
"bufio" | ||
"context" | ||
"fmt" | ||
"os" | ||
"os/signal" | ||
"syscall" | ||
|
||
"github.com/spf13/cobra" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
utilruntime "k8s.io/apimachinery/pkg/util/runtime" | ||
"k8s.io/cli-runtime/pkg/genericclioptions" | ||
clientgoscheme "k8s.io/client-go/kubernetes/scheme" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
|
||
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1" | ||
"github.com/clastix/kamaji/cmd/kubectl-kamaji/certificates" | ||
"github.com/clastix/kamaji/cmd/kubectl-kamaji/kubeconfig" | ||
"github.com/clastix/kamaji/cmd/kubectl-kamaji/token" | ||
) | ||
|
||
func main() { | ||
writer := bufio.NewWriter(os.Stdout) | ||
scheme := runtime.NewScheme() | ||
|
||
configFlags := genericclioptions.NewConfigFlags(true) | ||
|
||
clientConfig := configFlags.ToRawKubeConfigLoader() | ||
*configFlags.Namespace, _, _ = clientConfig.Namespace() | ||
|
||
restConfig, restErr := clientConfig.ClientConfig() | ||
if restErr != nil { | ||
_, _ = writer.WriteString(fmt.Sprintf("cannot retrieve REST configuration: %s", restErr.Error())) | ||
os.Exit(1) | ||
} | ||
|
||
k8sClient, clientErr := client.New(restConfig, client.Options{Scheme: scheme}) | ||
if clientErr != nil { | ||
_, _ = writer.WriteString(fmt.Sprintf("cannot generate Kubernetes configuration: %s", clientErr)) | ||
os.Exit(1) | ||
} | ||
|
||
rootCmd := NewCmd(scheme, configFlags) | ||
|
||
kubeconfigCmd := kubeconfig.NewKubeconfigGroup() | ||
kubeconfigCmd.AddCommand(kubeconfig.NewGetKubeconfig(writer, configFlags, k8sClient)) | ||
kubeconfigCmd.AddCommand(kubeconfig.NewRotateKubeconfig(writer, configFlags, k8sClient)) | ||
rootCmd.AddCommand(kubeconfigCmd) | ||
|
||
certificatesCmd := certificates.NewCertificatesGroup() | ||
certificatesCmd.AddCommand(certificates.NewRotateCertificates(writer, configFlags, k8sClient)) | ||
rootCmd.AddCommand(certificatesCmd) | ||
|
||
tokenCmd := token.NewTokenGroup() | ||
tokenCmd.AddCommand(token.NewTokenJoin(writer, configFlags, k8sClient)) | ||
rootCmd.AddCommand(tokenCmd) | ||
|
||
ctx, cancelFn := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) | ||
defer cancelFn() | ||
|
||
if _, err := rootCmd.ExecuteContextC(ctx); err != nil { | ||
cancelFn() | ||
os.Exit(1) //nolint:gocritic | ||
} | ||
|
||
_ = writer.Flush() | ||
} | ||
|
||
func NewCmd(scheme *runtime.Scheme, flags *genericclioptions.ConfigFlags) *cobra.Command { | ||
cmd := cobra.Command{ | ||
Use: "kubectl-kamaji", | ||
Aliases: []string{"kubectl kamaji"}, | ||
Short: "A plugin to manage your Kamaji Tenant Control Planes with ease.", | ||
PersistentPreRun: func(cmd *cobra.Command, args []string) { | ||
utilruntime.Must(clientgoscheme.AddToScheme(scheme)) | ||
utilruntime.Must(kamajiv1alpha1.AddToScheme(scheme)) | ||
}, | ||
} | ||
|
||
flags.AddFlags(cmd.PersistentFlags()) | ||
|
||
return &cmd | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// Copyright 2022 Clastix Labs | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
package token | ||
|
||
import ( | ||
"io" | ||
|
||
"github.com/spf13/cobra" | ||
"k8s.io/cli-runtime/pkg/genericclioptions" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
|
||
"github.com/clastix/kamaji/cmd/kubectl-kamaji/utils" | ||
"github.com/clastix/kamaji/pkg/cli" | ||
) | ||
|
||
func NewTokenJoin(writer io.Writer, flags *genericclioptions.ConfigFlags, k8sClient client.Client) *cobra.Command { | ||
var flavour string | ||
var skipExpired bool | ||
|
||
cmd := cobra.Command{ | ||
Use: "join {TCP_NAME}", | ||
Example: " kubectl [--namespace=$NAMESPACE] kamaji token join $TCP_NAME [--flavour=$FLAVOUR]", | ||
Short: "Print join command", | ||
Long: "Prints the required command to launch on a worker node to let it join to a Tenant Control Plane.\n" + | ||
"\n" + | ||
"The CLI flag --flavour allows to generate the command according to the flavour, yaki, or standard kubeadm.\n" + | ||
"When the yaki flavour is selected, the environment variable KUBERNETES_VERSION will reference the current Kubernetes version: " + | ||
"you can customize it according to your needs, as well as expanding the available environment variables (reference: https://github.com/clastix/yaki)", | ||
Args: cobra.ExactArgs(1), | ||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { | ||
return utils.ValidArgsFunction(k8sClient, *flags.Namespace)(cmd, args, toComplete) | ||
}, | ||
SilenceUsage: true, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
data, err := (&cli.Helper{Client: k8sClient}).JoinToken(cmd.Context(), *flags.Namespace, args[0], skipExpired, flavour) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
_, _ = writer.Write([]byte(data)) | ||
|
||
return nil | ||
}, | ||
} | ||
|
||
cmd.Flags().StringVar(&flavour, "flavour", "yaki", "The flavour to use for the join command, supported values: yaki, kubeadm.") | ||
cmd.Flags().BoolVar(&skipExpired, "skip-expired", true, "When enabled, expired bootstrap tokens will be ignored.") | ||
|
||
return &cmd | ||
} |
Oops, something went wrong.