diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 1c3e3cf6a04e2939a4f586dfeed8948dc3a15627..99fd21609b4e55e92b1616563f71c84dce8dfc41 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -74,6 +74,15 @@ jobs: - name: tk get kustomizations run: | ./bin/tk get kustomizations + - name: tk suspend kustomization + run: | + ./bin/tk suspend kustomization podinfo + - name: tk resume kustomization + run: | + ./bin/tk resume kustomization podinfo + - name: tk delete kustomization + run: | + ./bin/tk delete kustomization podinfo --silent - name: tk check run: | ./bin/tk check diff --git a/cmd/tk/delete.go b/cmd/tk/delete.go new file mode 100644 index 0000000000000000000000000000000000000000..61fa99c0427539cf5078ccf07304b68f36b12776 --- /dev/null +++ b/cmd/tk/delete.go @@ -0,0 +1,21 @@ +package main + +import ( + "github.com/spf13/cobra" +) + +var deleteCmd = &cobra.Command{ + Use: "delete", + Short: "Delete commands", +} + +var ( + deleteSilent bool +) + +func init() { + deleteCmd.PersistentFlags().BoolVarP(&deleteSilent, "silent", "", false, + "delete resource without asking for confirmation") + + rootCmd.AddCommand(deleteCmd) +} diff --git a/cmd/tk/delete_kustomization.go b/cmd/tk/delete_kustomization.go new file mode 100644 index 0000000000000000000000000000000000000000..684c7e746efad7e494b1027686c313033a616c69 --- /dev/null +++ b/cmd/tk/delete_kustomization.go @@ -0,0 +1,74 @@ +package main + +import ( + "context" + "fmt" + + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1" + "github.com/manifoldco/promptui" + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/types" +) + +var deleteKsCmd = &cobra.Command{ + Use: "kustomization [name]", + Aliases: []string{"ks"}, + Short: "Delete kustomization", + RunE: deleteKsCmdRun, +} + +func init() { + deleteCmd.AddCommand(deleteKsCmd) +} + +func deleteKsCmdRun(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return fmt.Errorf("kustomization name is required") + } + name := args[0] + + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + kubeClient, err := utils.kubeClient(kubeconfig) + if err != nil { + return err + } + + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + + var kustomization kustomizev1.Kustomization + err = kubeClient.Get(ctx, namespacedName, &kustomization) + if err != nil { + return err + } + + if !deleteSilent { + warning := "This action will remove the Kubernetes objects previously applied by this kustomization. " + if kustomization.Spec.Suspend { + warning = "" + } + prompt := promptui.Prompt{ + Label: fmt.Sprintf( + "%sAre you sure you want to delete the %s kustomization from the %s namespace", + warning, name, namespace, + ), + IsConfirm: true, + } + if _, err := prompt.Run(); err != nil { + return fmt.Errorf("aborting") + } + } + + logAction("deleting kustomization %s in %s namespace", name, namespace) + err = kubeClient.Delete(ctx, &kustomization) + if err != nil { + return err + } + logSuccess("kustomization deleted") + + return nil +} diff --git a/cmd/tk/get_kustomization.go b/cmd/tk/get_kustomization.go index c3101f360371ba0eb9e67e8c4548337333e2d5b8..9aa33c09ecadb064f9e36861e2a330a6f1eed0ca 100644 --- a/cmd/tk/get_kustomization.go +++ b/cmd/tk/get_kustomization.go @@ -43,6 +43,10 @@ func getKsCmdRun(cmd *cobra.Command, args []string) error { } for _, kustomization := range list.Items { + if kustomization.Spec.Suspend { + logSuccess("%s is suspended", kustomization.GetName()) + break + } isInitialized := false for _, condition := range kustomization.Status.Conditions { if condition.Type == kustomizev1.ReadyCondition { diff --git a/cmd/tk/resume.go b/cmd/tk/resume.go new file mode 100644 index 0000000000000000000000000000000000000000..ca1c39a3164f0604315e58069e5d2602fc16b171 --- /dev/null +++ b/cmd/tk/resume.go @@ -0,0 +1,14 @@ +package main + +import ( + "github.com/spf13/cobra" +) + +var resumeCmd = &cobra.Command{ + Use: "resume", + Short: "Resume commands", +} + +func init() { + rootCmd.AddCommand(resumeCmd) +} diff --git a/cmd/tk/resume_kustomization.go b/cmd/tk/resume_kustomization.go new file mode 100644 index 0000000000000000000000000000000000000000..fc2afda70f36907e445d8b2020b777c04a05ce98 --- /dev/null +++ b/cmd/tk/resume_kustomization.go @@ -0,0 +1,59 @@ +package main + +import ( + "context" + "fmt" + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1" + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/types" +) + +var resumeKsCmd = &cobra.Command{ + Use: "kustomization [name]", + Aliases: []string{"ks"}, + Short: "Resume kustomization", + Long: "The resume command marks a previously suspended Kustomization resource for reconciliation and waits for it to finish the apply.", + RunE: resumeKsCmdRun, +} + +func init() { + resumeCmd.AddCommand(resumeKsCmd) +} + +func resumeKsCmdRun(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return fmt.Errorf("kustomization name is required") + } + name := args[0] + + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + kubeClient, err := utils.kubeClient(kubeconfig) + if err != nil { + return err + } + + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + var kustomization kustomizev1.Kustomization + err = kubeClient.Get(ctx, namespacedName, &kustomization) + if err != nil { + return err + } + + logAction("resuming kustomization %s in %s namespace", name, namespace) + kustomization.Spec.Suspend = false + if err := kubeClient.Update(ctx, &kustomization); err != nil { + return err + } + logSuccess("kustomization resumed") + + if err := syncKsCmdRun(nil, []string{name}); err != nil { + return err + } + + return nil +} diff --git a/cmd/tk/suspend.go b/cmd/tk/suspend.go new file mode 100644 index 0000000000000000000000000000000000000000..f3cebe58de224e566eea65cb227305b02e00b7bc --- /dev/null +++ b/cmd/tk/suspend.go @@ -0,0 +1,14 @@ +package main + +import ( + "github.com/spf13/cobra" +) + +var suspendCmd = &cobra.Command{ + Use: "suspend", + Short: "Suspend commands", +} + +func init() { + rootCmd.AddCommand(suspendCmd) +} diff --git a/cmd/tk/suspend_kustomization.go b/cmd/tk/suspend_kustomization.go new file mode 100644 index 0000000000000000000000000000000000000000..07841f1a10f802dd0e6ac3963a8347fa420992d3 --- /dev/null +++ b/cmd/tk/suspend_kustomization.go @@ -0,0 +1,55 @@ +package main + +import ( + "context" + "fmt" + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1alpha1" + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/types" +) + +var suspendKsCmd = &cobra.Command{ + Use: "kustomization [name]", + Aliases: []string{"ks"}, + Short: "Suspend kustomization", + Long: "The suspend command disables the reconciliation of a Kustomization resource.", + RunE: suspendKsCmdRun, +} + +func init() { + suspendCmd.AddCommand(suspendKsCmd) +} + +func suspendKsCmdRun(cmd *cobra.Command, args []string) error { + if len(args) < 1 { + return fmt.Errorf("kustomization name is required") + } + name := args[0] + + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + kubeClient, err := utils.kubeClient(kubeconfig) + if err != nil { + return err + } + + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: name, + } + var kustomization kustomizev1.Kustomization + err = kubeClient.Get(ctx, namespacedName, &kustomization) + if err != nil { + return err + } + + logAction("suspending kustomization %s in %s namespace", name, namespace) + kustomization.Spec.Suspend = true + if err := kubeClient.Update(ctx, &kustomization); err != nil { + return err + } + logSuccess("kustomization suspended") + + return nil +}