From 9b81e3b56e263e37011b8f1fed036070b494a8d1 Mon Sep 17 00:00:00 2001 From: Michele Zanotti Date: Sun, 23 Jun 2024 18:30:01 +0200 Subject: [PATCH] Feat/deploy (#3) * refactor * duplicate deployments * fix: linting --- kubectl-duplicate/kubectl-duplicate.go | 2 +- pkg/cmd/common.go | 43 ++++++++++ pkg/cmd/deployment.go | 44 +++++++++++ pkg/cmd/pod.go | 105 +++---------------------- pkg/cmd/pod_test.go | 6 +- pkg/cmd/root.go | 8 +- pkg/cmd/shared.go | 79 +++++++++++++++++++ pkg/core/client.go | 22 ++++++ pkg/core/labels.go | 21 +++++ pkg/core/types.go | 61 ++++++++++++++ pkg/deployments/client.go | 101 ++++++++++++++++++++++++ pkg/pods/client.go | 55 ++++--------- pkg/pods/configurator.go | 34 ++++---- pkg/test/mocks/pod_client.go | 23 ++++-- pkg/utils/select.go | 44 +++++++++++ 15 files changed, 485 insertions(+), 163 deletions(-) create mode 100644 pkg/cmd/common.go create mode 100644 pkg/cmd/deployment.go create mode 100644 pkg/cmd/shared.go create mode 100644 pkg/core/client.go create mode 100644 pkg/core/labels.go create mode 100644 pkg/core/types.go create mode 100644 pkg/deployments/client.go create mode 100644 pkg/utils/select.go diff --git a/kubectl-duplicate/kubectl-duplicate.go b/kubectl-duplicate/kubectl-duplicate.go index 61d76d1..0d73852 100644 --- a/kubectl-duplicate/kubectl-duplicate.go +++ b/kubectl-duplicate/kubectl-duplicate.go @@ -22,7 +22,7 @@ import ( ) func main() { - rootCmd := cmd.NewRootCmd(nil) + rootCmd := cmd.NewRootCmd(nil, nil) err := rootCmd.Execute() if err != nil { os.Exit(1) diff --git a/pkg/cmd/common.go b/pkg/cmd/common.go new file mode 100644 index 0000000..ff76610 --- /dev/null +++ b/pkg/cmd/common.go @@ -0,0 +1,43 @@ +/* + * Copyright 2024 Michele Zanotti + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "github.com/spf13/cobra" + "github.com/telemaco019/duplik8s/pkg/cmd/flags" + "github.com/telemaco019/duplik8s/pkg/utils" +) + +func NewKubeOptions(cmd *cobra.Command, _ []string) (utils.KubeOptions, error) { + var err error + + o := utils.KubeOptions{} + o.Kubeconfig, err = cmd.Flags().GetString(flags.KUBECONFIG) + if err != nil { + return o, err + } + o.Kubecontext, err = cmd.Flags().GetString(flags.KUBECONTEXT) + if err != nil { + return o, err + } + o.Namespace, err = cmd.Flags().GetString(flags.NAMESPACE) + if err != nil { + return o, err + } + + return o, nil +} diff --git a/pkg/cmd/deployment.go b/pkg/cmd/deployment.go new file mode 100644 index 0000000..6c56427 --- /dev/null +++ b/pkg/cmd/deployment.go @@ -0,0 +1,44 @@ +/* + * Copyright 2024 Michele Zanotti + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "github.com/spf13/cobra" + "github.com/telemaco019/duplik8s/pkg/core" + "github.com/telemaco019/duplik8s/pkg/deployments" + "github.com/telemaco019/duplik8s/pkg/utils" +) + +func NewDeployCmd(client core.Duplik8sClient) *cobra.Command { + factory := func(opts utils.KubeOptions) (core.Duplik8sClient, error) { + if client == nil { + return deployments.NewClient(opts) + } + return client, nil + } + deployCmd := &cobra.Command{ + Use: "deploy", + Short: "Duplicate a Deployment.", + Args: cobra.MaximumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + run := newDuplicateCmd(factory, "Select a Deployment") + return run(cmd, args) + }, + } + addOverrideFlags(deployCmd) + return deployCmd +} diff --git a/pkg/cmd/pod.go b/pkg/cmd/pod.go index abefa4b..cbc7e16 100644 --- a/pkg/cmd/pod.go +++ b/pkg/cmd/pod.go @@ -17,113 +17,28 @@ package cmd import ( - "fmt" - "github.com/charmbracelet/huh" "github.com/spf13/cobra" - "github.com/telemaco019/duplik8s/pkg/cmd/flags" + "github.com/telemaco019/duplik8s/pkg/core" "github.com/telemaco019/duplik8s/pkg/pods" "github.com/telemaco019/duplik8s/pkg/utils" ) -func NewKubeOptions(cmd *cobra.Command, args []string) (utils.KubeOptions, error) { - var err error - - o := utils.KubeOptions{} - o.Kubeconfig, err = cmd.Flags().GetString(flags.KUBECONFIG) - if err != nil { - return o, err - } - o.Kubecontext, err = cmd.Flags().GetString(flags.KUBECONTEXT) - if err != nil { - return o, err +func NewPodCmd(podClient core.Duplik8sClient) *cobra.Command { + factory := func(opts utils.KubeOptions) (core.Duplik8sClient, error) { + if podClient == nil { + return pods.NewClient(opts) + } + return podClient, nil } - o.Namespace, err = cmd.Flags().GetString(flags.NAMESPACE) - if err != nil { - return o, err - } - - return o, nil -} - -func NewPodCmd(podClient pods.PodClient) *cobra.Command { podCmd := &cobra.Command{ Use: "pod", Short: "Duplicate a Pod.", Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - opts, err := NewKubeOptions(cmd, args) - if err != nil { - return err - } - if podClient == nil { - podClient, err = pods.NewClient(opts) - if err != nil { - return err - } - } - if err != nil { - return err - } - cmdOverride, err := cmd.Flags().GetStringSlice(flags.COMMAND_OVERRIDE) - if err != nil { - return err - } - argsOverride, err := cmd.Flags().GetStringSlice(flags.ARGS_OVERRIDE) - if err != nil { - return err - } - - // Avoid printing usage information on errors - cmd.SilenceUsage = true - options := pods.PodOverrideOptions{ - Command: cmdOverride, - Args: argsOverride, - } - - var podName string - if len(args) == 0 { - podName, err = selectPod(podClient, opts.Namespace) - if err != nil { - return err - } - } else { - podName = args[0] - } - - return podClient.DuplicatePod(podName, opts.Namespace, options) + run := newDuplicateCmd(factory, "Select a Pod") + return run(cmd, args) }, } - podCmd.Flags().StringSlice( - flags.COMMAND_OVERRIDE, - []string{"/bin/sh"}, - "Override the command of each container in the Pod.", - ) - podCmd.Flags().StringSlice( - flags.ARGS_OVERRIDE, - []string{"-c", "trap 'exit 0' INT TERM KILL; while true; do sleep 1; done"}, - "Override the command of each container in the Pod.", - ) - + addOverrideFlags(podCmd) return podCmd } - -func selectPod(client pods.PodClient, namespace string) (string, error) { - availablePods, err := client.ListPods(namespace) - if err != nil { - return "", err - } - if len(availablePods) == 0 { - return "", fmt.Errorf("no Pods found in namespace %q", namespace) - } - options := make([]huh.Option[string], len(availablePods)) - for i, p := range availablePods { - options[i] = huh.NewOption(p, p) - } - var selectedPod string - err = huh.NewSelect[string](). - Title(fmt.Sprintf("Select a Pod [%s]", namespace)). - Options(options...). - Value(&selectedPod). - Run() - return selectedPod, err -} diff --git a/pkg/cmd/pod_test.go b/pkg/cmd/pod_test.go index bb2160a..c40fcef 100644 --- a/pkg/cmd/pod_test.go +++ b/pkg/cmd/pod_test.go @@ -29,7 +29,7 @@ func Test_NoPodsAvailable(t *testing.T) { mocks.ListPodsResult{}, nil, ) - cmd := NewRootCmd(podClient) + cmd := NewRootCmd(podClient, nil) output, err := test.ExecuteCommand(cmd, "pod") assert.NotEmpty(t, output) assert.Error(t, err) @@ -40,7 +40,7 @@ func Test_Success(t *testing.T) { mocks.ListPodsResult{}, nil, ) - cmd := NewRootCmd(podClient) + cmd := NewRootCmd(podClient, nil) _, err := test.ExecuteCommand(cmd, "pod", "pod-1") assert.NoError(t, err) } @@ -50,7 +50,7 @@ func Test_DuplicateError(t *testing.T) { mocks.ListPodsResult{}, fmt.Errorf("error"), ) - cmd := NewRootCmd(podClient) + cmd := NewRootCmd(podClient, nil) _, err := test.ExecuteCommand(cmd, "pod", "pod-1") assert.EqualError(t, err, "error") } diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index 008b931..c352be9 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -18,14 +18,15 @@ package cmd import ( "github.com/spf13/cobra" - "github.com/telemaco019/duplik8s/pkg/pods" + "github.com/telemaco019/duplik8s/pkg/core" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/client-go/util/homedir" "path/filepath" ) func NewRootCmd( - c pods.PodClient, + podClient core.Duplik8sClient, + deployClient core.Duplik8sClient, ) *cobra.Command { rootCmd := &cobra.Command{ Use: "kubectl-duplicate", @@ -50,7 +51,8 @@ func NewRootCmd( configFlags.AddFlags(rootCmd.PersistentFlags()) // add subcommands - rootCmd.AddCommand(NewPodCmd(c)) + rootCmd.AddCommand(NewPodCmd(podClient)) + rootCmd.AddCommand(NewDeployCmd(deployClient)) return rootCmd } diff --git a/pkg/cmd/shared.go b/pkg/cmd/shared.go new file mode 100644 index 0000000..0231c20 --- /dev/null +++ b/pkg/cmd/shared.go @@ -0,0 +1,79 @@ +/* + * Copyright 2024 Michele Zanotti + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cmd + +import ( + "github.com/spf13/cobra" + "github.com/telemaco019/duplik8s/pkg/cmd/flags" + "github.com/telemaco019/duplik8s/pkg/core" + "github.com/telemaco019/duplik8s/pkg/utils" +) + +type duplik8sClientFactory func(opts utils.KubeOptions) (core.Duplik8sClient, error) + +func newDuplicateCmd(factory duplik8sClientFactory, selectMessage string) func(cmd *cobra.Command, args []string) error { + return func(cmd *cobra.Command, args []string) error { + opts, err := NewKubeOptions(cmd, args) + if err != nil { + return err + } + client, err := factory(opts) + if err != nil { + return err + } + cmdOverride, err := cmd.Flags().GetStringSlice(flags.COMMAND_OVERRIDE) + if err != nil { + return err + } + argsOverride, err := cmd.Flags().GetStringSlice(flags.ARGS_OVERRIDE) + if err != nil { + return err + } + + // Avoid printing usage information on errors + cmd.SilenceUsage = true + options := core.PodOverrideOptions{ + Command: cmdOverride, + Args: argsOverride, + } + + var obj core.DuplicableObject + if len(args) == 0 { + obj, err = utils.SelectItem(client, opts.Namespace, selectMessage) + if err != nil { + return err + } + } else { + obj = core.NewPod(args[0], opts.Namespace) + } + + return client.Duplicate(obj, options) + } +} + +func addOverrideFlags(cmd *cobra.Command) { + cmd.Flags().StringSlice( + flags.COMMAND_OVERRIDE, + []string{"/bin/sh"}, + "Override the command of each container in the Pod.", + ) + cmd.Flags().StringSlice( + flags.ARGS_OVERRIDE, + []string{"-c", "trap 'exit 0' INT TERM KILL; while true; do sleep 1; done"}, + "Override the command of each container in the Pod.", + ) +} diff --git a/pkg/core/client.go b/pkg/core/client.go new file mode 100644 index 0000000..6764c99 --- /dev/null +++ b/pkg/core/client.go @@ -0,0 +1,22 @@ +/* + * Copyright 2024 Michele Zanotti + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package core + +type Duplik8sClient interface { + List(namespace string) ([]DuplicableObject, error) + Duplicate(obj DuplicableObject, opts PodOverrideOptions) error +} diff --git a/pkg/core/labels.go b/pkg/core/labels.go new file mode 100644 index 0000000..0216e62 --- /dev/null +++ b/pkg/core/labels.go @@ -0,0 +1,21 @@ +/* + * Copyright 2024 Michele Zanotti + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package core + +const ( + LABEL_DUPLICATED = "telemaco019.github.com/duplik8ted" +) diff --git a/pkg/core/types.go b/pkg/core/types.go new file mode 100644 index 0000000..b86e18e --- /dev/null +++ b/pkg/core/types.go @@ -0,0 +1,61 @@ +/* + * Copyright 2024 Michele Zanotti + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package core + +import v1 "k8s.io/api/core/v1" + +type DuplicableObjectKind string + +const ( + KindDeployment DuplicableObjectKind = "Deployment" + KindPod DuplicableObjectKind = "Pod" +) + +type PodOverrideOptions struct { + // Command overrides the default command of each container. + Command []string + // Args overrides the default args of each container. + Args []string + // ReadinessProbe overrides the readiness probe of each container. + ReadinessProbe *v1.Probe + // LivenessProbe overrides the liveness probe of each container. + LivenessProbe *v1.Probe + // StartupProbe overrides the startup probe of each container. + StartupProbe *v1.Probe +} + +type DuplicableObject struct { + Name string + Namespace string + Kind DuplicableObjectKind +} + +func NewPod(name, namespace string) DuplicableObject { + return DuplicableObject{ + Kind: KindPod, + Name: name, + Namespace: namespace, + } +} + +func NewDeployment(name, namespace string) DuplicableObject { + return DuplicableObject{ + Kind: KindDeployment, + Name: name, + Namespace: namespace, + } +} diff --git a/pkg/deployments/client.go b/pkg/deployments/client.go new file mode 100644 index 0000000..a615976 --- /dev/null +++ b/pkg/deployments/client.go @@ -0,0 +1,101 @@ +/* + * Copyright 2024 Michele Zanotti + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package deployments + +import ( + "context" + "fmt" + "github.com/telemaco019/duplik8s/pkg/core" + "github.com/telemaco019/duplik8s/pkg/pods" + "github.com/telemaco019/duplik8s/pkg/utils" + appsv1 "k8s.io/api/apps/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +type DeploymentClient struct { + clientset *kubernetes.Clientset + ctx context.Context +} + +func NewClient(opts utils.KubeOptions) (*DeploymentClient, error) { + clientset, err := utils.NewClientset(opts.Kubeconfig, opts.Kubecontext) + if err != nil { + return nil, err + } + return &DeploymentClient{ + clientset: clientset, + ctx: context.Background(), + }, nil +} + +func (c *DeploymentClient) List(namespace string) ([]core.DuplicableObject, error) { + deployments, err := c.clientset.AppsV1().Deployments(namespace).List(c.ctx, metav1.ListOptions{}) + if err != nil { + return nil, err + } + var objs []core.DuplicableObject + for _, d := range deployments.Items { + objs = append(objs, core.NewDeployment(d.Name, d.Namespace)) + } + return objs, nil +} + +func (c *DeploymentClient) Duplicate(obj core.DuplicableObject, opts core.PodOverrideOptions) error { + fmt.Printf("duplicating deployment %s\n", obj.Name) + + // fetch the Deployment + deploy, err := c.clientset.AppsV1().Deployments(obj.Namespace).Get(c.ctx, obj.Name, metav1.GetOptions{}) + if err != nil { + return err + } + if deploy.Labels[core.LABEL_DUPLICATED] == "true" { + return fmt.Errorf("deployment %s is already duplicated", obj.Name) + } + + // create a new Deployment and override the spec + newName := fmt.Sprintf("%s-duplik8ted", deploy.Name) + newDeploy := appsv1.Deployment{ + TypeMeta: metav1.TypeMeta{ + Kind: "Deployment", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: newName, + Namespace: deploy.Namespace, + Labels: map[string]string{ + core.LABEL_DUPLICATED: "true", + }, + }, + Spec: deploy.Spec, + } + + // override the spec of the deployment's pod + configurator := pods.NewConfigurator(c.clientset, opts) + err = configurator.OverrideSpec(c.ctx, obj.Namespace, &newDeploy.Spec.Template.Spec) + if err != nil { + return err + } + + // create the new deployment + _, err = c.clientset.AppsV1().Deployments(obj.Namespace).Create(c.ctx, &newDeploy, metav1.CreateOptions{}) + if err != nil { + return err + } + fmt.Printf("deployment %q duplicated in %q\n", obj.Name, newName) + return nil +} diff --git a/pkg/pods/client.go b/pkg/pods/client.go index 3c9c2fa..8a0ace4 100644 --- a/pkg/pods/client.go +++ b/pkg/pods/client.go @@ -19,72 +19,51 @@ package pods import ( "context" "fmt" + "github.com/telemaco019/duplik8s/pkg/core" "github.com/telemaco019/duplik8s/pkg/utils" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" ) -const ( - LABEL_DUPLICATED = "telemaco019.github.com/duplik8ted" -) - -type PodClient interface { - ListPods(namespace string) ([]string, error) - DuplicatePod(podName string, namespace string, opts PodOverrideOptions) error -} - -type podClient struct { +type PodClient struct { clientset *kubernetes.Clientset ctx context.Context } -func NewClient(opts utils.KubeOptions) (PodClient, error) { +func NewClient(opts utils.KubeOptions) (*PodClient, error) { clientset, err := utils.NewClientset(opts.Kubeconfig, opts.Kubecontext) if err != nil { return nil, err } - return &podClient{ + return &PodClient{ clientset: clientset, ctx: context.Background(), }, nil } -type PodOverrideOptions struct { - // Command overrides the default command of each container. - Command []string - // Args overrides the default args of each container. - Args []string - // ReadinessProbe overrides the readiness probe of each container. - ReadinessProbe *v1.Probe - // LivenessProbe overrides the liveness probe of each container. - LivenessProbe *v1.Probe - // StartupProbe overrides the startup probe of each container. - StartupProbe *v1.Probe -} - -func (c *podClient) ListPods(namespace string) ([]string, error) { +func (c *PodClient) List(namespace string) ([]core.DuplicableObject, error) { pods, err := c.clientset.CoreV1().Pods(namespace).List(c.ctx, metav1.ListOptions{}) if err != nil { return nil, err } - var podNames []string + var objs []core.DuplicableObject for _, pod := range pods.Items { - podNames = append(podNames, pod.Name) + objs = append(objs, core.NewPod(pod.Name, pod.Namespace)) } - return podNames, nil + return objs, nil } -func (c *podClient) DuplicatePod(podName string, namespace string, opts PodOverrideOptions) error { - fmt.Printf("duplicating Pod %s\n", podName) +func (c *PodClient) Duplicate(obj core.DuplicableObject, opts core.PodOverrideOptions) error { + fmt.Printf("duplicating pod %s\n", obj.Name) // fetch the pod - pod, err := c.clientset.CoreV1().Pods(namespace).Get(c.ctx, podName, metav1.GetOptions{}) + pod, err := c.clientset.CoreV1().Pods(obj.Namespace).Get(c.ctx, obj.Name, metav1.GetOptions{}) if err != nil { return err } - if pod.Labels[LABEL_DUPLICATED] == "true" { - return fmt.Errorf("pod %s is already duplicated", podName) + if pod.Labels[core.LABEL_DUPLICATED] == "true" { + return fmt.Errorf("pod %s is already duplicated", obj.Name) } // create a new pod and override the spec @@ -98,15 +77,15 @@ func (c *podClient) DuplicatePod(podName string, namespace string, opts PodOverr Name: newName, Namespace: pod.Namespace, Labels: map[string]string{ - LABEL_DUPLICATED: "true", + core.LABEL_DUPLICATED: "true", }, }, Spec: pod.Spec, } // override the pod spec - configurator := NewPodConfigurator(c.clientset, opts) - err = configurator.OverrideSpec(c.ctx, &newPod) + configurator := NewConfigurator(c.clientset, opts) + err = configurator.OverrideSpec(c.ctx, obj.Namespace, &newPod.Spec) if err != nil { return err } @@ -116,6 +95,6 @@ func (c *podClient) DuplicatePod(podName string, namespace string, opts PodOverr if err != nil { return err } - fmt.Printf("pod %s duplicated in %s\n", podName, newName) + fmt.Printf("pod %q duplicated in %q\n", obj.Name, newName) return nil } diff --git a/pkg/pods/configurator.go b/pkg/pods/configurator.go index 868e103..9ea5dcd 100644 --- a/pkg/pods/configurator.go +++ b/pkg/pods/configurator.go @@ -18,6 +18,7 @@ package pods import ( "context" + "github.com/telemaco019/duplik8s/pkg/core" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -25,30 +26,30 @@ import ( type PodConfigurator struct { clientset *kubernetes.Clientset - options PodOverrideOptions + options core.PodOverrideOptions } -func NewPodConfigurator(clientset *kubernetes.Clientset, options PodOverrideOptions) PodConfigurator { +func NewConfigurator(clientset *kubernetes.Clientset, options core.PodOverrideOptions) PodConfigurator { return PodConfigurator{ clientset: clientset, options: options, } } -func (c PodConfigurator) OverrideSpec(ctx context.Context, pod *v1.Pod) error { +func (c PodConfigurator) OverrideSpec(ctx context.Context, namespace string, podSpec *v1.PodSpec) error { // Override command if c.options.Command != nil { - for i := range pod.Spec.Containers { - pod.Spec.Containers[i].Command = c.options.Command - pod.Spec.Containers[i].Args = c.options.Args - pod.Spec.Containers[i].ReadinessProbe = c.options.ReadinessProbe - pod.Spec.Containers[i].LivenessProbe = c.options.LivenessProbe - pod.Spec.Containers[i].ReadinessProbe = c.options.ReadinessProbe - pod.Spec.Containers[i].StartupProbe = c.options.StartupProbe + for i := range podSpec.Containers { + podSpec.Containers[i].Command = c.options.Command + podSpec.Containers[i].Args = c.options.Args + podSpec.Containers[i].ReadinessProbe = c.options.ReadinessProbe + podSpec.Containers[i].LivenessProbe = c.options.LivenessProbe + podSpec.Containers[i].ReadinessProbe = c.options.ReadinessProbe + podSpec.Containers[i].StartupProbe = c.options.StartupProbe } } - hasMountOncePvc, err := c.hasMountOncePvc(ctx, *pod) + hasMountOncePvc, err := c.hasMountOncePvc(ctx, namespace, *podSpec) if err != nil { return err } @@ -56,21 +57,18 @@ func (c PodConfigurator) OverrideSpec(ctx context.Context, pod *v1.Pod) error { // If the Pod does not have any PVC with mount once policy, then remove the node name // to allow the scheduler to schedule the pod on any node if !hasMountOncePvc { - pod.Spec.NodeName = "" + podSpec.NodeName = "" } - // Override restart policy - pod.Spec.RestartPolicy = v1.RestartPolicyNever - return nil } -func (c PodConfigurator) hasMountOncePvc(ctx context.Context, pod v1.Pod) (bool, error) { - for _, volume := range pod.Spec.Volumes { +func (c PodConfigurator) hasMountOncePvc(ctx context.Context, namespace string, podSpec v1.PodSpec) (bool, error) { + for _, volume := range podSpec.Volumes { if volume.PersistentVolumeClaim != nil { pvc, err := c.clientset. CoreV1(). - PersistentVolumeClaims(pod.Namespace). + PersistentVolumeClaims(namespace). Get(ctx, volume.PersistentVolumeClaim.ClaimName, metav1.GetOptions{}) if err != nil { return false, err diff --git a/pkg/test/mocks/pod_client.go b/pkg/test/mocks/pod_client.go index 53d304f..e340d27 100644 --- a/pkg/test/mocks/pod_client.go +++ b/pkg/test/mocks/pod_client.go @@ -16,13 +16,26 @@ package mocks -import "github.com/telemaco019/duplik8s/pkg/pods" +import ( + "github.com/telemaco019/duplik8s/pkg/core" +) type ListPodsResult struct { - Pods []string + Objs []core.DuplicableObject Err error } +func NewListPodResults(pods []string, namespace string, err error) ListPodsResult { + var objs = make([]core.DuplicableObject, 0) + for _, pod := range pods { + objs = append(objs, core.NewPod(pod, namespace)) + } + return ListPodsResult{ + Objs: objs, + Err: err, + } +} + type PodClient struct { ListPodsResult ListPodsResult DuplicatePodResult error @@ -38,10 +51,10 @@ func NewPodClient( } } -func (c *PodClient) ListPods(namespace string) ([]string, error) { - return c.ListPodsResult.Pods, c.ListPodsResult.Err +func (c *PodClient) List(_ string) ([]core.DuplicableObject, error) { + return c.ListPodsResult.Objs, c.ListPodsResult.Err } -func (c *PodClient) DuplicatePod(podName string, namespace string, opts pods.PodOverrideOptions) error { +func (c *PodClient) Duplicate(_ core.DuplicableObject, __ core.PodOverrideOptions) error { return c.DuplicatePodResult } diff --git a/pkg/utils/select.go b/pkg/utils/select.go new file mode 100644 index 0000000..19fcde7 --- /dev/null +++ b/pkg/utils/select.go @@ -0,0 +1,44 @@ +/* + * Copyright 2024 Michele Zanotti + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package utils + +import ( + "fmt" + "github.com/charmbracelet/huh" + "github.com/telemaco019/duplik8s/pkg/core" +) + +func SelectItem(client core.Duplik8sClient, namespace, selectMessage string) (core.DuplicableObject, error) { + var selected = core.DuplicableObject{} + objs, err := client.List(namespace) + if err != nil { + return selected, err + } + if len(objs) == 0 { + return selected, fmt.Errorf("no Pods found in namespace %q", namespace) + } + options := make([]huh.Option[core.DuplicableObject], len(objs)) + for i, o := range objs { + options[i] = huh.NewOption(o.Name, o) + } + err = huh.NewSelect[core.DuplicableObject](). + Title(fmt.Sprintf("%s [%s]", selectMessage, namespace)). + Options(options...). + Value(&selected). + Run() + return selected, err +}