diff --git a/cmd/kubernetes/kubernetes.go b/cmd/kubernetes/kubernetes.go index 5093c8f..72e50a6 100644 --- a/cmd/kubernetes/kubernetes.go +++ b/cmd/kubernetes/kubernetes.go @@ -142,6 +142,11 @@ func init() { kubernetesNodePoolInstanceListCmd.Flags().StringVarP(&nodePoolID, "node-pool-id", "p", "", "the ID of the node pool.") kubernetesNodePoolCmd.AddCommand(kubernetesNodePoolListCmd) + + // Define the flags for the kubernetesRemoveCmd + kubernetesRemoveCmd.Flags().BoolVar(&deleteVolumes, "delete-volumes", false, "Delete dependent volumes") + kubernetesRemoveCmd.Flags().BoolVar(&keepFirewalls, "keep-firewalls", false, "Keep dependent firewalls") + kubernetesRemoveCmd.Flags().BoolVar(&keepKubeconfig, "keep-kubeconfig", false, "Keep kubeconfig") } func getKubernetesList(value string) []string { diff --git a/cmd/kubernetes/kubernetes_remove.go b/cmd/kubernetes/kubernetes_remove.go index d995907..9974414 100644 --- a/cmd/kubernetes/kubernetes_remove.go +++ b/cmd/kubernetes/kubernetes_remove.go @@ -14,7 +14,15 @@ import ( "github.com/spf13/cobra" ) -var kuberneteList []utility.ObjecteList +var ( + deleteVolumes bool // Flag to delete dependent volumes + keepFirewalls bool // Flag to keep dependent firewalls + keepKubeconfig bool // Flag to keep kubeconfig +) + +var kuberneteList []utility.ObjecteList // List to store the Kubernetes clusters to be deleted + +// Command definition for removing a Kubernetes cluster var kubernetesRemoveCmd = &cobra.Command{ Use: "remove", Aliases: []string{"rm", "delete", "destroy"}, @@ -30,6 +38,7 @@ var kubernetesRemoveCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { utility.EnsureCurrentRegion() + // Create a client to interact with the Civo API client, err := config.CivoAPIClient() if common.RegionSet != "" { client.Region = common.RegionSet @@ -39,11 +48,12 @@ var kubernetesRemoveCmd = &cobra.Command{ os.Exit(1) } - if len(args) == 1 { - kubernetesCluster, err := client.FindKubernetesCluster(args[0]) + // Find and store the Kubernetes clusters to be deleted + for _, clusterName := range args { + kubernetesCluster, err := client.FindKubernetesCluster(clusterName) if err != nil { if errors.Is(err, civogo.ZeroMatchesError) { - utility.Error("sorry there is no %s Kubernetes cluster in your account", utility.Red(args[0])) + utility.Error("sorry there is no %s Kubernetes cluster in your account", utility.Red(clusterName)) os.Exit(1) } if errors.Is(err, civogo.MultipleMatchesError) { @@ -52,53 +62,112 @@ var kubernetesRemoveCmd = &cobra.Command{ } } kuberneteList = append(kuberneteList, utility.ObjecteList{ID: kubernetesCluster.ID, Name: kubernetesCluster.Name}) - } else { - for _, v := range args { - kubernetesCluster, err := client.FindKubernetesCluster(v) - if err == nil { - kuberneteList = append(kuberneteList, utility.ObjecteList{ID: kubernetesCluster.ID, Name: kubernetesCluster.Name}) - } - } } + // Collect names of the clusters to be deleted for confirmation message kubernetesNameList := []string{} for _, v := range kuberneteList { kubernetesNameList = append(kubernetesNameList, v.Name) } - var volsNameList []string - for _, v := range kuberneteList { - vols, err := client.ListVolumesForCluster(v.ID) - if err != nil { - utility.Error("error getting the list of dangling volumes: %s", err) - os.Exit(1) - } - for _, v := range vols { - volsNameList = append(volsNameList, v.Name) - } - if vols != nil { - utility.YellowConfirm("There are volume(s) attached to this cluster. Consider deleting or detaching these before deleting the cluster:\n%s\n", utility.Green(strings.Join(volsNameList, "\n"))) - } - } - + // Confirm the deletion with the user if utility.UserConfirmedDeletion(fmt.Sprintf("Kubernetes %s", pluralize.Pluralize(len(kuberneteList), "cluster")), common.DefaultYes, strings.Join(kubernetesNameList, ", ")) { - for _, v := range kuberneteList { - _, err = client.DeleteKubernetesCluster(v.ID) + + // Delete the Kubernetes cluster + _, err := client.DeleteKubernetesCluster(v.ID) if err != nil { utility.Error("error deleting the kubernetes cluster: %s", err) os.Exit(1) } + + /* Poll for the deletion status + This is required because, if we try to delete other things like firewalls before + the cluster is completely deleted, we will encounter errors like "database_firewall_inuse_by_cluster". + */ + for { + _, err := client.FindKubernetesCluster(v.Name) + if err != nil { + if errors.Is(err, civogo.ZeroMatchesError) { + break // Cluster is deleted + } + } + } + + // Delete volumes if --delete-volumes flag is set + if deleteVolumes { + volumes, err := client.ListVolumesForCluster(v.ID) + if err != nil { + if !errors.Is(err, civogo.ZeroMatchesError) { + utility.Error("Error listing volumes for cluster %s: %s", v.Name, err) + } + } else { + for _, volume := range volumes { + _, err := client.DeleteVolume(volume.ID) + if err != nil { + utility.Error("Error deleting volume %s: %s", volume.Name, err) + } + fmt.Printf("%s volume deleted", volume.ID) + } + } + } + + // Output volumes left behind if deleteVolumes flag is not set + if !deleteVolumes { + volumes, err := client.ListVolumesForCluster(v.ID) + if err != nil { + if !errors.Is(err, civogo.ZeroMatchesError) { + utility.Error("Error listing volumes for cluster %s: %s", v.Name, err) + } + } else if len(volumes) > 0 { + fmt.Fprintf(os.Stderr, "Volumes left behind for Kubernetes cluster %s:\n", v.Name) + for _, volume := range volumes { + fmt.Fprintf(os.Stderr, "- %s\n", volume.ID) + } + fmt.Fprintf(os.Stderr, "Consider using '--delete-volumes' flag next time to delete them automatically.\n") + } + } + + // Delete firewalls if --keep-firewalls flag is not set + if !keepFirewalls { + firewall, err := client.FindFirewall(v.Name) + if err != nil { + if errors.Is(err, civogo.MultipleMatchesError) { + utility.Error("Error deleting the firewall: %v. Please delete the firewall manually by visiting: https://dashboard.civo.com/firewalls", err) + } else { + utility.Error("Error finding the firewall: %v", err) + } + } else if firewall.ClusterCount == 0 { + _, err := client.DeleteFirewall(firewall.ID) + if err != nil { + utility.Error("Error deleting firewall %s: %v", firewall.Name, err) + } else { + fmt.Fprintf(os.Stderr, "Firewall %s deleted. If you want to keep the firewall in the future, please use '--keep-firewalls' flag.\n", firewall.ID) + } + } + } + + // Delete kubeconfig if --keep-kubeconfig flag is not set + if !keepKubeconfig { + kubeconfigPath := fmt.Sprintf("%s/.kube/config", os.Getenv("HOME")) + err := os.Remove(kubeconfigPath) + if err != nil && !os.IsNotExist(err) { + utility.Error("Error deleting kubeconfig: %s", err) + } else { + fmt.Fprintf(os.Stderr, "Kubeconfig file deleted. If you want to keep the kubeconfig in the future, please use '--keep-kubeconfig' flag.\n") + } + } } + // Output the result of the deletion ow := utility.NewOutputWriter() - for _, v := range kuberneteList { ow.StartLine() ow.AppendDataWithLabel("id", v.ID, "ID") ow.AppendDataWithLabel("name", v.Name, "Name") } + // Format the output based on the selected format switch common.OutputFormat { case "json": if len(kuberneteList) == 1 {