Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions cmd/ciReleaseDeleteResources.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package cmd

import (
"fmt"
"log"
"os"

"github.com/spf13/cobra"
"github.com/wunderio/silta-cli/internal/common"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp" // gcp auth provider

helmAction "helm.sh/helm/v3/pkg/action"
helmCli "helm.sh/helm/v3/pkg/cli"
)

var ciReleaseDeleteResourcesCmd = &cobra.Command{
Use: "delete-resources",
Short: "Delete orphaned release resources",
Long: `Deletes release resources based on labels ("release", "app.kubernetes.io/instance" and "app=<release-name>-es" (for Elasticsearch storage))
This command can be used to clean up resources when helm release configmaps are absent.
`,
Run: func(cmd *cobra.Command, args []string) {
releaseName, _ := cmd.Flags().GetString("release-name")
namespace, _ := cmd.Flags().GetString("namespace")
deletePVCs, _ := cmd.Flags().GetBool("delete-pvcs")
dryRun, _ := cmd.Flags().GetBool("dry-run")

clientset, err := common.GetKubeClient()
if err != nil {
log.Fatalf("failed to get kube client: %v", err)
}

// Helm client init logic
settings := helmCli.New()
settings.SetNamespace(namespace) // Ensure Helm uses the correct namespace

actionConfig := new(helmAction.Configuration)
if err := actionConfig.Init(settings.RESTClientGetter(), namespace, os.Getenv("HELM_DRIVER"), log.Printf); err != nil {
log.Printf("%+v", err)
os.Exit(1)
}

fmt.Printf("Finding orphaned resources for release %s in namespace %s\n", releaseName, namespace)

err = common.DeleteOrphanedReleaseResources(clientset, actionConfig, namespace, releaseName, deletePVCs, dryRun)
if err != nil {
log.Fatalf("Error removing a release: %s", err)
}

},
}

func init() {
ciReleaseCmd.AddCommand(ciReleaseDeleteResourcesCmd)

ciReleaseDeleteResourcesCmd.Flags().String("release-name", "", "Release name")
ciReleaseDeleteResourcesCmd.Flags().String("namespace", "", "Project name (namespace, i.e. \"drupal-project\")")
ciReleaseDeleteResourcesCmd.Flags().Bool("delete-pvcs", true, "Delete PVCs (default: true)")
ciReleaseDeleteResourcesCmd.Flags().Bool("dry-run", true, "Dry run (default: true)")

ciReleaseDeleteResourcesCmd.MarkFlagRequired("release-name")
ciReleaseDeleteResourcesCmd.MarkFlagRequired("namespace")
}
1 change: 1 addition & 0 deletions docs/silta_ci_release.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ silta ci release [flags]
* [silta ci](silta_ci.md) - Silta CI Commands
* [silta ci release clean-failed](silta_ci_release_clean-failed.md) - Clean failed releases
* [silta ci release delete](silta_ci_release_delete.md) - Delete a release
* [silta ci release delete-resources](silta_ci_release_delete-resources.md) - Delete orphhaned release resources
* [silta ci release deploy](silta_ci_release_deploy.md) - Deploy release
* [silta ci release diff](silta_ci_release_diff.md) - Diff release resources
* [silta ci release environmentname](silta_ci_release_environmentname.md) - Return environment name
Expand Down
29 changes: 29 additions & 0 deletions docs/silta_ci_release_delete-resources.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## silta ci release delete-resources

Delete orphhaned release resources

```
silta ci release delete-resources [flags]
```

### Options

```
--delete-pvcs Delete PVCs (default: true) (default true)
--dry-run Dry run (default: true) (default true)
-h, --help help for delete-resources
--namespace string Project name (namespace, i.e. "drupal-project")
--release-name string Release name
```

### Options inherited from parent commands

```
--debug Print variables, do not execute external commands, rather print them
--use-env Use environment variables for value assignment (default true)
```

### SEE ALSO

* [silta ci release](silta_ci_release.md) - CI release related commands

4 changes: 4 additions & 0 deletions docs/silta_cloud_login.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ Log in to kubernetes cluster using different methods:
- "--aks-tenant-id" flag or "AKS_TENANT_ID" environment variable
- "--aks-sp-app-id" flag or "AKS_SP_APP_ID" environment variable
- "--aks-sp-password" flag or "AKS_SP_PASSWORD" environment variable

After login, the connection is tested by running "kubectl can-i get pods" command,
disable with "--test-connection=false" flag.


```
Expand All @@ -57,6 +60,7 @@ silta cloud login [flags]
-h, --help help for login
--kubeconfig string Kubernetes config content (plaintext, base64 encoded)
--kubeconfigpath string Kubernetes config path (default "~/.kube/config")
--test-connection Test connection after login (default true)
```

### Options inherited from parent commands
Expand Down
255 changes: 255 additions & 0 deletions internal/common/ciReleaseFunctions.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"log"
"os"
"time"

v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
Expand Down Expand Up @@ -127,3 +128,257 @@ func FailedReleaseCleanup(releaseName string, namespace string) {
}
}
}

func DeleteOrphanedReleaseResources(kubernetesClient *kubernetes.Clientset, helmClient *helmAction.Configuration, namespace string, releaseName string, deletePVCs bool, dryRun bool) error {
// Select related resources by label selectors
selectorLabels := []string{
"release",
"app.kubernetes.io/instance",
"app" + "=" + releaseName + "-es",
}

for _, l := range selectorLabels {
selector := l + "=" + releaseName
// Delete deployments
dpList, _ := kubernetesClient.AppsV1().Deployments(namespace).List(context.TODO(), v1.ListOptions{
LabelSelector: selector,
})
for _, v := range dpList.Items {
if dryRun {
fmt.Printf("Dry run: deployment/%s\n", v.Name)
} else {
fmt.Printf("Removing deployment/%s\n", v.Name)
kubernetesClient.AppsV1().Deployments(namespace).Delete(context.TODO(), v.Name, v1.DeleteOptions{})
}
}

// Delete statefulsets
stsList, _ := kubernetesClient.AppsV1().StatefulSets(namespace).List(context.TODO(), v1.ListOptions{
LabelSelector: selector,
})
for _, v := range stsList.Items {
if dryRun {
fmt.Printf("Dry run: statefulset/%s\n", v.Name)
} else {
log.Printf("Removing statefulset/%s\n", v.Name)
kubernetesClient.AppsV1().StatefulSets(namespace).Delete(context.TODO(), v.Name, v1.DeleteOptions{})
}
}

// Delete cronjobs
cjList, _ := kubernetesClient.BatchV1().CronJobs(namespace).List(context.TODO(), v1.ListOptions{
LabelSelector: selector,
})
for _, v := range cjList.Items {
if dryRun {
fmt.Printf("Dry run: cronjob/%s\n", v.Name)
} else {
fmt.Printf("Removing cronjob/%s\n", v.Name)
kubernetesClient.BatchV1().CronJobs(namespace).Delete(context.TODO(), v.Name, v1.DeleteOptions{})
}
}

// Delete jobs
jobList, _ := kubernetesClient.BatchV1().Jobs(namespace).List(context.TODO(), v1.ListOptions{
LabelSelector: selector,
})
for _, v := range jobList.Items {
if dryRun {
fmt.Printf("Dry run: job/%s\n", v.Name)
} else {
fmt.Printf("Removing job/%s\n", v.Name)
kubernetesClient.BatchV1().Jobs(namespace).Delete(context.TODO(), v.Name, v1.DeleteOptions{})
}
}

// Delete horizontal pod autoscalers
hpaList, _ := kubernetesClient.AutoscalingV1().HorizontalPodAutoscalers(namespace).List(context.TODO(), v1.ListOptions{
LabelSelector: selector,
})
for _, v := range hpaList.Items {
if dryRun {
fmt.Printf("Dry run: horizontalpodautoscaler/%s\n", v.Name)
} else {
fmt.Printf("Removing horizontalpodautoscaler/%s\n", v.Name)
kubernetesClient.AutoscalingV1().HorizontalPodAutoscalers(namespace).Delete(context.TODO(), v.Name, v1.DeleteOptions{})
}
}

// Delete ingresses
ingressList, _ := kubernetesClient.NetworkingV1().Ingresses(namespace).List(context.TODO(), v1.ListOptions{
LabelSelector: selector,
})
for _, v := range ingressList.Items {
if dryRun {
fmt.Printf("Dry run: ingress/%s\n", v.Name)
} else {
fmt.Printf("Removing ingress/%s\n", v.Name)
kubernetesClient.NetworkingV1().Ingresses(namespace).Delete(context.TODO(), v.Name, v1.DeleteOptions{})
}
}

// Delete pods
podList, _ := kubernetesClient.CoreV1().Pods(namespace).List(context.TODO(), v1.ListOptions{
LabelSelector: selector,
})
for _, v := range podList.Items {
if dryRun {
fmt.Printf("Dry run: pod/%s\n", v.Name)
} else {
fmt.Printf("Removing pod/%s\n", v.Name)
kubernetesClient.CoreV1().Pods(namespace).Delete(context.TODO(), v.Name, v1.DeleteOptions{})
}
}

// Delete services
svcList, _ := kubernetesClient.CoreV1().Services(namespace).List(context.TODO(), v1.ListOptions{
LabelSelector: selector,
})
for _, v := range svcList.Items {
if dryRun {
fmt.Printf("Dry run: service/%s\n", v.Name)
} else {
fmt.Printf("Removing service/%s\n", v.Name)
kubernetesClient.CoreV1().Services(namespace).Delete(context.TODO(), v.Name, v1.DeleteOptions{})
}
}

// Delete backendconfigs
bcList, _ := kubernetesClient.NetworkingV1().Ingresses(namespace).List(context.TODO(), v1.ListOptions{
LabelSelector: selector,
})
for _, v := range bcList.Items {
if dryRun {
fmt.Printf("Dry run: backendconfig/%s\n", v.Name)
} else {
fmt.Printf("Removing backendconfig/%s\n", v.Name)
kubernetesClient.NetworkingV1().Ingresses(namespace).Delete(context.TODO(), v.Name, v1.DeleteOptions{})
}
}

// Delete configmaps
cmList, _ := kubernetesClient.CoreV1().ConfigMaps(namespace).List(context.TODO(), v1.ListOptions{
LabelSelector: selector,
})
for _, v := range cmList.Items {
if dryRun {
fmt.Printf("Dry run: configmap/%s\n", v.Name)
} else {
fmt.Printf("Removing configmap/%s\n", v.Name)
kubernetesClient.CoreV1().ConfigMaps(namespace).Delete(context.TODO(), v.Name, v1.DeleteOptions{})
}
}

// Delete secrets
secretList, _ := kubernetesClient.CoreV1().Secrets(namespace).List(context.TODO(), v1.ListOptions{
LabelSelector: selector,
})
for _, v := range secretList.Items {
if dryRun {
fmt.Printf("Dry run: secret/%s\n", v.Name)
} else {
fmt.Printf("Removing secret/%s\n", v.Name)
kubernetesClient.CoreV1().Secrets(namespace).Delete(context.TODO(), v.Name, v1.DeleteOptions{})
}
}

// Delete cerficates
certList, _ := kubernetesClient.CoreV1().Secrets(namespace).List(context.TODO(), v1.ListOptions{
LabelSelector: selector,
})
for _, v := range certList.Items {
if dryRun {
fmt.Printf("Dry run: certificate/%s\n", v.Name)
} else {
fmt.Printf("Removing certificate/%s\n", v.Name)
kubernetesClient.CoreV1().Secrets(namespace).Delete(context.TODO(), v.Name, v1.DeleteOptions{})
}
}

// Delete persistent volume claims
if deletePVCs {
pvcList, _ := kubernetesClient.CoreV1().PersistentVolumeClaims(namespace).List(context.TODO(), v1.ListOptions{
LabelSelector: selector,
})
for _, v := range pvcList.Items {
if dryRun {
fmt.Printf("Dry run: persistentvolumeclaim/%s\n", v.Name)
} else {
fmt.Printf("Removing persistentvolumeclaim/%s\n", v.Name)
kubernetesClient.CoreV1().PersistentVolumeClaims(namespace).Delete(context.TODO(), v.Name, v1.DeleteOptions{})
}
}
}

// Delete persistent volumes
if deletePVCs {
pvList, _ := kubernetesClient.CoreV1().PersistentVolumes().List(context.TODO(), v1.ListOptions{
LabelSelector: selector,
})
for _, v := range pvList.Items {
if dryRun {
fmt.Printf("Dry run: persistentvolume/%s\n", v.Name)
} else {
fmt.Printf("Removing persistentvolume/%s\n", v.Name)
kubernetesClient.CoreV1().PersistentVolumes().Delete(context.TODO(), v.Name, v1.DeleteOptions{})
time.Sleep(1 * time.Second)
}
}
}

// Delete network policies
npList, _ := kubernetesClient.NetworkingV1().NetworkPolicies(namespace).List(context.TODO(), v1.ListOptions{
LabelSelector: selector,
})
for _, v := range npList.Items {
if dryRun {
fmt.Printf("Dry run: networkpolicy/%s\n", v.Name)
continue
} else {
fmt.Printf("Removing networkpolicy/%s\n", v.Name)
kubernetesClient.NetworkingV1().NetworkPolicies(namespace).Delete(context.TODO(), v.Name, v1.DeleteOptions{})
}
}

// Delete rolebindings
rbList, _ := kubernetesClient.RbacV1().RoleBindings(namespace).List(context.TODO(), v1.ListOptions{
LabelSelector: selector,
})
for _, v := range rbList.Items {
if dryRun {
fmt.Printf("Dry run: rolebinding/%s\n", v.Name)
} else {
fmt.Printf("Removing rolebinding/%s\n", v.Name)
kubernetesClient.RbacV1().RoleBindings(namespace).Delete(context.TODO(), v.Name, v1.DeleteOptions{})
}
}

// Delete roles
roleList, _ := kubernetesClient.RbacV1().Roles(namespace).List(context.TODO(), v1.ListOptions{
LabelSelector: selector,
})
for _, v := range roleList.Items {
if dryRun {
fmt.Printf("Dry run: role/%s\n", v.Name)
} else {
fmt.Printf("Removing role/%s\n", v.Name)
kubernetesClient.RbacV1().Roles(namespace).Delete(context.TODO(), v.Name, v1.DeleteOptions{})
}
}

// Delete serviceaccounts
saList, _ := kubernetesClient.CoreV1().ServiceAccounts(namespace).List(context.TODO(), v1.ListOptions{
LabelSelector: selector,
})
for _, v := range saList.Items {
if dryRun {
fmt.Printf("Dry run: serviceaccount/%s\n", v.Name)
} else {
fmt.Printf("Removing serviceaccount/%s\n", v.Name)
kubernetesClient.CoreV1().ServiceAccounts(namespace).Delete(context.TODO(), v.Name, v1.DeleteOptions{})
}
}
}

return nil
}