From 78c62753ac133c07c0425bb0fb226393203062d2 Mon Sep 17 00:00:00 2001 From: Melvin Zottola Date: Thu, 5 Oct 2023 14:51:52 +0200 Subject: [PATCH] feat: Be able to delete bunch of services --- cmd/application_delete.go | 59 ++++++++++++++++++++-- cmd/container_delete.go | 55 ++++++++++++++++++-- cmd/cronjob_delete.go | 58 ++++++++++++++++++++-- cmd/database_delete.go | 56 +++++++++++++++++++-- cmd/lifecycle_delete.go | 59 ++++++++++++++++++++-- utils/qovery.go | 102 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 372 insertions(+), 17 deletions(-) diff --git a/cmd/application_delete.go b/cmd/application_delete.go index aadd9cba..6782bd15 100644 --- a/cmd/application_delete.go +++ b/cmd/application_delete.go @@ -4,10 +4,13 @@ import ( "context" "fmt" "os" + "strings" + "time" "github.com/pterm/pterm" - "github.com/qovery/qovery-cli/utils" "github.com/spf13/cobra" + + "github.com/qovery/qovery-cli/utils" ) var applicationDeleteCmd = &cobra.Command{ @@ -23,6 +26,18 @@ var applicationDeleteCmd = &cobra.Command{ panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 } + if applicationName == "" && applicationNames == "" { + utils.PrintlnError(fmt.Errorf("use neither --application \"\" nor --applications \", \"")) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + + if applicationName != "" && applicationNames != "" { + utils.PrintlnError(fmt.Errorf("you can't use --application and --applications at the same time")) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + client := utils.GetQoveryClient(tokenType, token) _, _, envId, err := getOrganizationProjectEnvironmentContextResourcesIds(client) @@ -32,6 +47,41 @@ var applicationDeleteCmd = &cobra.Command{ panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 } + if applicationNames != "" { + // wait until service is ready + for { + if utils.IsEnvironmentInATerminalState(envId, client) { + break + } + + utils.Println(fmt.Sprintf("Waiting for environment %s to be ready..", pterm.FgBlue.Sprintf(envId))) + time.Sleep(5 * time.Second) + } + + applications, _, err := client.ApplicationsApi.ListApplication(context.Background(), envId).Execute() + var serviceIds []string + for _, applicationName := range strings.Split(applicationNames, ",") { + trimmedApplicationName := strings.TrimSpace(applicationName) + serviceIds = append(serviceIds, utils.FindByApplicationName(applications.GetResults(), trimmedApplicationName).Id) + } + + // stop multiple services + _, err = utils.DeleteServices(client, envId, serviceIds, utils.ApplicationType) + if watchFlag { + utils.WatchEnvironment(envId, "unused", client) + } else { + utils.Println(fmt.Sprintf("Deleting applications %s in progress..", pterm.FgBlue.Sprintf(applicationNames))) + } + + if err != nil { + utils.PrintlnError(err) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + + return + } + applications, _, err := client.ApplicationsApi.ListApplication(context.Background(), envId).Execute() if err != nil { @@ -51,6 +101,10 @@ var applicationDeleteCmd = &cobra.Command{ msg, err := utils.DeleteService(client, envId, application.Id, utils.ApplicationType, watchFlag) + if watchFlag { + utils.WatchEnvironment(envId, "unused", client) + } + if err != nil { utils.PrintlnError(err) os.Exit(1) @@ -76,7 +130,6 @@ func init() { applicationDeleteCmd.Flags().StringVarP(&projectName, "project", "", "", "Project Name") applicationDeleteCmd.Flags().StringVarP(&environmentName, "environment", "", "", "Environment Name") applicationDeleteCmd.Flags().StringVarP(&applicationName, "application", "n", "", "Application Name") + applicationDeleteCmd.Flags().StringVarP(&applicationNames, "applications", "", "", "Application Names (comma separated) Example: --applications \"app1,app2,app3\"") applicationDeleteCmd.Flags().BoolVarP(&watchFlag, "watch", "w", false, "Watch application status until it's ready or an error occurs") - - _ = applicationDeleteCmd.MarkFlagRequired("application") } diff --git a/cmd/container_delete.go b/cmd/container_delete.go index 31c76b0a..2d997855 100644 --- a/cmd/container_delete.go +++ b/cmd/container_delete.go @@ -4,10 +4,13 @@ import ( "context" "fmt" "os" + "strings" + "time" "github.com/pterm/pterm" - "github.com/qovery/qovery-cli/utils" "github.com/spf13/cobra" + + "github.com/qovery/qovery-cli/utils" ) var containerDeleteCmd = &cobra.Command{ @@ -23,6 +26,18 @@ var containerDeleteCmd = &cobra.Command{ panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 } + if containerName == "" && containerNames == "" { + utils.PrintlnError(fmt.Errorf("use neither --container \"\" nor --containers \", \"")) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + + if containerName != "" && containerNames != "" { + utils.PrintlnError(fmt.Errorf("you can't use --container and --containers at the same time")) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + client := utils.GetQoveryClient(tokenType, token) _, _, envId, err := getOrganizationProjectEnvironmentContextResourcesIds(client) @@ -32,6 +47,41 @@ var containerDeleteCmd = &cobra.Command{ panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 } + if containerNames != "" { + // wait until service is ready + for { + if utils.IsEnvironmentInATerminalState(envId, client) { + break + } + + utils.Println(fmt.Sprintf("Waiting for environment %s to be ready..", pterm.FgBlue.Sprintf(envId))) + time.Sleep(5 * time.Second) + } + + containers, _, err := client.ContainersApi.ListContainer(context.Background(), envId).Execute() + var serviceIds []string + for _, containerName := range strings.Split(containerNames, ",") { + trimmedContainerName := strings.TrimSpace(containerName) + serviceIds = append(serviceIds, utils.FindByContainerName(containers.GetResults(), trimmedContainerName).Id) + } + + _, err = utils.DeleteServices(client, envId, serviceIds, utils.ContainerType) + + if watchFlag { + utils.WatchEnvironment(envId, "unused", client) + } else { + utils.Println(fmt.Sprintf("Deleting containers %s in progress..", pterm.FgBlue.Sprintf(containerNames))) + } + + if err != nil { + utils.PrintlnError(err) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + + return + } + containers, _, err := client.ContainersApi.ListContainer(context.Background(), envId).Execute() if err != nil { @@ -76,7 +126,6 @@ func init() { containerDeleteCmd.Flags().StringVarP(&projectName, "project", "", "", "Project Name") containerDeleteCmd.Flags().StringVarP(&environmentName, "environment", "", "", "Environment Name") containerDeleteCmd.Flags().StringVarP(&containerName, "container", "n", "", "Container Name") + containerDeleteCmd.Flags().StringVarP(&containerNames, "containers", "", "", "Container Names (comma separated) (ex: --containers \"container1,container2\")") containerDeleteCmd.Flags().BoolVarP(&watchFlag, "watch", "w", false, "Watch container status until it's ready or an error occurs") - - _ = containerDeleteCmd.MarkFlagRequired("container") } diff --git a/cmd/cronjob_delete.go b/cmd/cronjob_delete.go index 2847eb59..f366b73a 100644 --- a/cmd/cronjob_delete.go +++ b/cmd/cronjob_delete.go @@ -2,11 +2,15 @@ package cmd import ( "fmt" - "github.com/pterm/pterm" "os" + "strings" + "time" + + "github.com/pterm/pterm" - "github.com/qovery/qovery-cli/utils" "github.com/spf13/cobra" + + "github.com/qovery/qovery-cli/utils" ) var cronjobDeleteCmd = &cobra.Command{ @@ -22,6 +26,18 @@ var cronjobDeleteCmd = &cobra.Command{ panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 } + if cronjobName == "" && cronjobNames == "" { + utils.PrintlnError(fmt.Errorf("use neither --cronjob \"\" nor --cronjobs \", \"")) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + + if cronjobName != "" && cronjobNames != "" { + utils.PrintlnError(fmt.Errorf("you can't use --cronjob and --cronjobs at the same time")) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + client := utils.GetQoveryClient(tokenType, token) _, _, envId, err := getOrganizationProjectEnvironmentContextResourcesIds(client) @@ -31,6 +47,41 @@ var cronjobDeleteCmd = &cobra.Command{ panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 } + if cronjobNames != "" { + // wait until service is ready + for { + if utils.IsEnvironmentInATerminalState(envId, client) { + break + } + + utils.Println(fmt.Sprintf("Waiting for environment %s to be ready..", pterm.FgBlue.Sprintf(envId))) + time.Sleep(5 * time.Second) + } + + cronjobs, err := ListCronjobs(envId, client) + var serviceIds []string + for _, cronjobName := range strings.Split(cronjobNames, ",") { + trimmedCronjobName := strings.TrimSpace(cronjobName) + serviceIds = append(serviceIds, utils.FindByJobName(cronjobs, trimmedCronjobName).Id) + } + + _, err = utils.DeleteServices(client, envId, serviceIds, utils.JobType) + + if watchFlag { + utils.WatchEnvironment(envId, "unused", client) + } else { + utils.Println(fmt.Sprintf("Deleting cronjobs %s in progress..", pterm.FgBlue.Sprintf(cronjobNames))) + } + + if err != nil { + utils.PrintlnError(err) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + + return + } + cronjobs, err := ListCronjobs(envId, client) if err != nil { @@ -75,7 +126,6 @@ func init() { cronjobDeleteCmd.Flags().StringVarP(&projectName, "project", "", "", "Project Name") cronjobDeleteCmd.Flags().StringVarP(&environmentName, "environment", "", "", "Environment Name") cronjobDeleteCmd.Flags().StringVarP(&cronjobName, "cronjob", "n", "", "Cronjob Name") + cronjobDeleteCmd.Flags().StringVarP(&cronjobNames, "cronjobs", "", "", "Cronjob Names (comma separated) (ex: --cronjobs \"cron1,cron2\")") cronjobDeleteCmd.Flags().BoolVarP(&watchFlag, "watch", "w", false, "Watch cronjob status until it's ready or an error occurs") - - _ = cronjobDeleteCmd.MarkFlagRequired("cronjob") } diff --git a/cmd/database_delete.go b/cmd/database_delete.go index 1d5a54a4..63e61039 100644 --- a/cmd/database_delete.go +++ b/cmd/database_delete.go @@ -4,10 +4,13 @@ import ( "context" "fmt" "os" + "strings" + "time" "github.com/pterm/pterm" - "github.com/qovery/qovery-cli/utils" "github.com/spf13/cobra" + + "github.com/qovery/qovery-cli/utils" ) var databaseDeleteCmd = &cobra.Command{ @@ -23,6 +26,18 @@ var databaseDeleteCmd = &cobra.Command{ panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 } + if databaseName == "" && databaseNames == "" { + utils.PrintlnError(fmt.Errorf("use neither --database \"\" nor --databases \", \"")) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + + if databaseName != "" && databaseNames != "" { + utils.PrintlnError(fmt.Errorf("you can't use --database and --databases at the same time")) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + client := utils.GetQoveryClient(tokenType, token) _, _, envId, err := getOrganizationProjectEnvironmentContextResourcesIds(client) @@ -32,6 +47,42 @@ var databaseDeleteCmd = &cobra.Command{ panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 } + if databaseNames != "" { + // wait until service is ready + for { + if utils.IsEnvironmentInATerminalState(envId, client) { + break + } + + utils.Println(fmt.Sprintf("Waiting for environment %s to be ready..", pterm.FgBlue.Sprintf(envId))) + time.Sleep(5 * time.Second) + } + + databases, _, err := client.DatabasesApi.ListDatabase(context.Background(), envId).Execute() + var serviceIds []string + for _, databaseName := range strings.Split(databaseNames, ",") { + trimmedDatabaseName := strings.TrimSpace(databaseName) + serviceIds = append(serviceIds, utils.FindByDatabaseName(databases.GetResults(), trimmedDatabaseName).Id) + } + + // stop multiple services + _, err = utils.DeleteServices(client, envId, serviceIds, utils.DatabaseType) + + if watchFlag { + utils.WatchEnvironment(envId, "unused", client) + } else { + utils.Println(fmt.Sprintf("Deleting databases %s in progress..", pterm.FgBlue.Sprintf(databaseNames))) + } + + if err != nil { + utils.PrintlnError(err) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + + return + } + databases, _, err := client.DatabasesApi.ListDatabase(context.Background(), envId).Execute() if err != nil { @@ -76,7 +127,6 @@ func init() { databaseDeleteCmd.Flags().StringVarP(&projectName, "project", "", "", "Project Name") databaseDeleteCmd.Flags().StringVarP(&environmentName, "environment", "", "", "Environment Name") databaseDeleteCmd.Flags().StringVarP(&databaseName, "database", "n", "", "Database Name") + databaseDeleteCmd.Flags().StringVarP(&databaseNames, "databases", "", "", "Database Names (comma separated) Example: --databases \"db1,db2\"") databaseDeleteCmd.Flags().BoolVarP(&watchFlag, "watch", "w", false, "Watch database status until it's ready or an error occurs") - - _ = databaseDeleteCmd.MarkFlagRequired("database") } diff --git a/cmd/lifecycle_delete.go b/cmd/lifecycle_delete.go index 97e3b3e5..31b35aa4 100644 --- a/cmd/lifecycle_delete.go +++ b/cmd/lifecycle_delete.go @@ -2,11 +2,15 @@ package cmd import ( "fmt" - "github.com/pterm/pterm" "os" + "strings" + "time" + + "github.com/pterm/pterm" - "github.com/qovery/qovery-cli/utils" "github.com/spf13/cobra" + + "github.com/qovery/qovery-cli/utils" ) var lifecycleDeleteCmd = &cobra.Command{ @@ -22,6 +26,18 @@ var lifecycleDeleteCmd = &cobra.Command{ panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 } + if lifecycleName == "" && lifecycleNames == "" { + utils.PrintlnError(fmt.Errorf("use neither --lifecycle \"\" nor --lifecycles \", \"")) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + + if lifecycleName != "" && lifecycleNames != "" { + utils.PrintlnError(fmt.Errorf("you can't use --lifecycle and --lifecycles at the same time")) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + client := utils.GetQoveryClient(tokenType, token) _, _, envId, err := getOrganizationProjectEnvironmentContextResourcesIds(client) @@ -31,6 +47,42 @@ var lifecycleDeleteCmd = &cobra.Command{ panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 } + if lifecycleNames != "" { + // wait until service is ready + for { + if utils.IsEnvironmentInATerminalState(envId, client) { + break + } + + utils.Println(fmt.Sprintf("Waiting for environment %s to be ready..", pterm.FgBlue.Sprintf(envId))) + time.Sleep(5 * time.Second) + } + + lifecycles, err := ListLifecycleJobs(envId, client) + var serviceIds []string + for _, lifecycleName := range strings.Split(lifecycleNames, ",") { + trimmedLifecycleName := strings.TrimSpace(lifecycleName) + serviceIds = append(serviceIds, utils.FindByJobName(lifecycles, trimmedLifecycleName).Id) + } + + // stop multiple services + _, err = utils.DeleteServices(client, envId, serviceIds, utils.JobType) + + if watchFlag { + utils.WatchEnvironment(envId, "unused", client) + } else { + utils.Println(fmt.Sprintf("Deleting lifecycle jobs %s in progress..", pterm.FgBlue.Sprintf(lifecycleNames))) + } + + if err != nil { + utils.PrintlnError(err) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + + return + } + lifecycles, err := ListLifecycleJobs(envId, client) if err != nil { @@ -75,7 +127,6 @@ func init() { lifecycleDeleteCmd.Flags().StringVarP(&projectName, "project", "", "", "Project Name") lifecycleDeleteCmd.Flags().StringVarP(&environmentName, "environment", "", "", "Environment Name") lifecycleDeleteCmd.Flags().StringVarP(&lifecycleName, "lifecycle", "n", "", "Lifecycle Job Name") + lifecycleDeleteCmd.Flags().StringVarP(&lifecycleNames, "lifecycles", "", "", "Lifecycle Job Names (comma separated) (ex: --lifecycles \"lifecycle1,lifecycle2\")") lifecycleDeleteCmd.Flags().BoolVarP(&watchFlag, "watch", "w", false, "Watch lifecycle job status until it's ready or an error occurs") - - _ = lifecycleDeleteCmd.MarkFlagRequired("lifecycle") } diff --git a/utils/qovery.go b/utils/qovery.go index f36e2b56..d7baeaa0 100644 --- a/utils/qovery.go +++ b/utils/qovery.go @@ -1582,6 +1582,108 @@ func DeleteService(client *qovery.APIClient, envId string, serviceId string, ser return DeleteService(client, envId, serviceId, serviceType, watchFlag) } +func DeleteServices(client *qovery.APIClient, envId string, serviceIds []string, serviceType ServiceType) (string, error) { + statuses, _, err := client.EnvironmentMainCallsApi.GetEnvironmentStatuses(context.Background(), envId).Execute() + + if err != nil { + return "", err + } + + cannotDelete := false + serviceIdsSet := map[string]struct{}{} + for _, value := range serviceIds { + serviceIdsSet[value] = struct{}{} + } + + if IsTerminalState(statuses.GetEnvironment().State) { + switch serviceType { + case ApplicationType: + for _, application := range statuses.GetApplications() { + if _, ok := serviceIdsSet[application.Id]; ok && !IsTerminalState(application.State) { + cannotDelete = true + } + } + if !cannotDelete { + _, err := client.EnvironmentActionsApi. + DeleteSelectedServices(context.Background(), envId). + EnvironmentServiceIdsAllRequest(qovery.EnvironmentServiceIdsAllRequest{ + ApplicationIds: serviceIds, + }). + Execute() + if err != nil { + return "", err + } + + return "", nil + } + case DatabaseType: + for _, database := range statuses.GetDatabases() { + if _, ok := serviceIdsSet[database.Id]; ok && !IsTerminalState(database.State) { + cannotDelete = true + } + } + if !cannotDelete { + _, err := client.EnvironmentActionsApi. + DeleteSelectedServices(context.Background(), envId). + EnvironmentServiceIdsAllRequest(qovery.EnvironmentServiceIdsAllRequest{ + DatabaseIds: serviceIds, + }). + Execute() + if err != nil { + return "", err + } + + return "", nil + } + case ContainerType: + for _, container := range statuses.GetContainers() { + if _, ok := serviceIdsSet[container.Id]; ok && !IsTerminalState(container.State) { + cannotDelete = true + } + } + if !cannotDelete { + _, err := client.EnvironmentActionsApi. + DeleteSelectedServices(context.Background(), envId). + EnvironmentServiceIdsAllRequest(qovery.EnvironmentServiceIdsAllRequest{ + ContainerIds: serviceIds, + }). + Execute() + if err != nil { + return "", err + } + + return "", nil + } + case JobType: + for _, job := range statuses.GetJobs() { + if _, ok := serviceIdsSet[job.Id]; ok && !IsTerminalState(job.State) { + cannotDelete = true + } + } + if !cannotDelete { + _, err := client.EnvironmentActionsApi. + DeleteSelectedServices(context.Background(), envId). + EnvironmentServiceIdsAllRequest(qovery.EnvironmentServiceIdsAllRequest{ + JobIds: serviceIds, + }). + Execute() + if err != nil { + return "", err + } + + return "", nil + } + } + } + + PrintlnInfo("waiting for previous deployment to be completed...") + + // sleep here to avoid too many requests + time.Sleep(5 * time.Second) + + return DeleteServices(client, envId, serviceIds, serviceType) +} + func DeployService(client *qovery.APIClient, envId string, serviceId string, serviceType ServiceType, request interface{}, watchFlag bool) (string, error) { statuses, _, err := client.EnvironmentMainCallsApi.GetEnvironmentStatuses(context.Background(), envId).Execute()