From c00c2dd697bb3e2c70533520947243386cbd5674 Mon Sep 17 00:00:00 2001 From: Fabien FLEUREAU Date: Mon, 23 Feb 2026 15:48:21 +0100 Subject: [PATCH 1/2] feat(stage): add skip/unskip commands and show skipped section in list --- cmd/environment_stage_list.go | 73 +++++++++++++++++++++++----- cmd/environment_stage_skip.go | 86 +++++++++++++++++++++++++++++++++ cmd/environment_stage_unskip.go | 86 +++++++++++++++++++++++++++++++++ 3 files changed, 233 insertions(+), 12 deletions(-) create mode 100644 cmd/environment_stage_skip.go create mode 100644 cmd/environment_stage_unskip.go diff --git a/cmd/environment_stage_list.go b/cmd/environment_stage_list.go index a9e98b0e..d1769752 100644 --- a/cmd/environment_stage_list.go +++ b/cmd/environment_stage_list.go @@ -47,6 +47,35 @@ var environmentStageListCmd = &cobra.Command{ return } + // Collect all skipped services across all stages + var skippedData [][]string + for _, stage := range stages.GetResults() { + for _, service := range stage.GetServices() { + if service.GetIsSkipped() { + skippedData = append(skippedData, []string{ + service.Id, + service.GetServiceType(), + utils.GetServiceNameByIdAndType(client, service.GetServiceId(), service.GetServiceType()), + stage.GetName(), + }) + } + } + } + + // Show skipped services section first + if len(skippedData) > 0 { + pterm.DefaultSection.WithBottomPadding(0).Println("Skipped services (excluded from environment-level deployments)") + utils.Println("") + err = utils.PrintTable([]string{"Id", "Type", "Name", "Stage"}, skippedData) + utils.Println("") + if err != nil { + utils.PrintlnError(err) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + } + + // Show each stage with only non-skipped services for _, stage := range stages.GetResults() { pterm.DefaultSection.WithBottomPadding(0).Println("deployment stage " + strconv.Itoa(int(stage.GetDeploymentOrder()+1)) + ": \"" + stage.GetName() + "\"") utils.Println("Stage id: " + stage.GetId()) @@ -58,14 +87,16 @@ var environmentStageListCmd = &cobra.Command{ var data [][]string for _, service := range stage.GetServices() { - data = append(data, []string{ - service.Id, - service.GetServiceType(), - utils.GetServiceNameByIdAndType(client, service.GetServiceId(), service.GetServiceType()), - }) + if !service.GetIsSkipped() { + data = append(data, []string{ + service.Id, + service.GetServiceType(), + utils.GetServiceNameByIdAndType(client, service.GetServiceId(), service.GetServiceType()), + }) + } } - if len(stage.GetServices()) == 0 { + if len(data) == 0 { utils.Println("") } else { err = utils.PrintTable([]string{"Id", "Type", "Name"}, data) @@ -83,17 +114,30 @@ var environmentStageListCmd = &cobra.Command{ } func getEnvironmentStageJsonOutput(client qovery.APIClient, stages []qovery.DeploymentStageResponse) string { + var skippedServices []interface{} var results []interface{} for idx, stage := range stages { var services []interface{} for _, service := range stage.Services { - services = append(services, map[string]interface{}{ - "id": service.ServiceId, - "type": service.ServiceType, - "name": utils.GetServiceNameByIdAndType(&client, service.GetServiceId(), service.GetServiceType()), - }) + entry := map[string]interface{}{ + "id": service.ServiceId, + "type": service.ServiceType, + "name": utils.GetServiceNameByIdAndType(&client, service.GetServiceId(), service.GetServiceType()), + "is_skipped": service.GetIsSkipped(), + } + services = append(services, entry) + + if service.GetIsSkipped() { + skippedEntry := map[string]interface{}{ + "id": service.ServiceId, + "type": service.ServiceType, + "name": utils.GetServiceNameByIdAndType(&client, service.GetServiceId(), service.GetServiceType()), + "stage": stage.Name, + } + skippedServices = append(skippedServices, skippedEntry) + } } results = append(results, map[string]interface{}{ @@ -105,7 +149,12 @@ func getEnvironmentStageJsonOutput(client qovery.APIClient, stages []qovery.Depl }) } - j, err := json.Marshal(results) + output := map[string]interface{}{ + "skipped_services": skippedServices, + "stages": results, + } + + j, err := json.Marshal(output) if err != nil { utils.PrintlnError(err) diff --git a/cmd/environment_stage_skip.go b/cmd/environment_stage_skip.go new file mode 100644 index 00000000..99f9f684 --- /dev/null +++ b/cmd/environment_stage_skip.go @@ -0,0 +1,86 @@ +package cmd + +import ( + "context" + "errors" + "os" + + "github.com/qovery/qovery-cli/utils" + "github.com/qovery/qovery-client-go" + "github.com/spf13/cobra" +) + +var environmentStageSkipCmd = &cobra.Command{ + Use: "skip", + Short: "Skip service from environment-level deployments", + Long: "Mark a service as skipped so it is excluded from environment-level bulk deployments while staying in its current stage. Use 'environment stage unskip' to reverse.", + Run: func(cmd *cobra.Command, args []string) { + utils.Capture(cmd) + + tokenType, token, err := utils.GetAccessToken() + if err != nil { + utils.PrintlnError(err) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + + client := utils.GetQoveryClient(tokenType, token) + _, _, environmentId, err := getOrganizationProjectEnvironmentContextResourcesIds(client) + + if err != nil { + utils.PrintlnError(err) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + + stages, _, err := client.DeploymentStageMainCallsAPI.ListEnvironmentDeploymentStage(context.Background(), environmentId).Execute() + + if err != nil { + utils.PrintlnError(err) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + + var service *qovery.DeploymentStageServiceResponse + var currentStageId string + for _, stage := range stages.GetResults() { + service, _ = getServiceByName(client, stage.GetServices(), serviceName) + if service != nil { + currentStageId = stage.GetId() + break + } + } + + if service == nil { + utils.PrintlnError(errors.New("service not found")) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + + req := qovery.AttachServiceToDeploymentStageRequest{} + req.SetIsSkipped(true) + + _, _, err = client.DeploymentStageMainCallsAPI. + AttachServiceToDeploymentStage(context.Background(), currentStageId, service.GetServiceId()). + AttachServiceToDeploymentStageRequest(req). + Execute() + + if err != nil { + utils.PrintlnError(err) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + + utils.Println("Service \"" + serviceName + "\" is now skipped from environment-level deployments") + }, +} + +func init() { + environmentStageCmd.AddCommand(environmentStageSkipCmd) + environmentStageSkipCmd.Flags().StringVarP(&organizationName, "organization", "", "", "Organization Name") + environmentStageSkipCmd.Flags().StringVarP(&projectName, "project", "", "", "Project Name") + environmentStageSkipCmd.Flags().StringVarP(&environmentName, "environment", "", "", "Environment Name") + environmentStageSkipCmd.Flags().StringVarP(&serviceName, "name", "n", "", "Service Name") + + _ = environmentStageSkipCmd.MarkFlagRequired("name") +} diff --git a/cmd/environment_stage_unskip.go b/cmd/environment_stage_unskip.go new file mode 100644 index 00000000..a9d82402 --- /dev/null +++ b/cmd/environment_stage_unskip.go @@ -0,0 +1,86 @@ +package cmd + +import ( + "context" + "errors" + "os" + + "github.com/qovery/qovery-cli/utils" + "github.com/qovery/qovery-client-go" + "github.com/spf13/cobra" +) + +var environmentStageUnskipCmd = &cobra.Command{ + Use: "unskip", + Short: "Unskip service from environment-level deployments", + Long: "Remove the skipped flag from a service so it is included again in environment-level bulk deployments.", + Run: func(cmd *cobra.Command, args []string) { + utils.Capture(cmd) + + tokenType, token, err := utils.GetAccessToken() + if err != nil { + utils.PrintlnError(err) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + + client := utils.GetQoveryClient(tokenType, token) + _, _, environmentId, err := getOrganizationProjectEnvironmentContextResourcesIds(client) + + if err != nil { + utils.PrintlnError(err) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + + stages, _, err := client.DeploymentStageMainCallsAPI.ListEnvironmentDeploymentStage(context.Background(), environmentId).Execute() + + if err != nil { + utils.PrintlnError(err) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + + var service *qovery.DeploymentStageServiceResponse + var currentStageId string + for _, stage := range stages.GetResults() { + service, _ = getServiceByName(client, stage.GetServices(), serviceName) + if service != nil { + currentStageId = stage.GetId() + break + } + } + + if service == nil { + utils.PrintlnError(errors.New("service not found")) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + + req := qovery.AttachServiceToDeploymentStageRequest{} + req.SetIsSkipped(false) + + _, _, err = client.DeploymentStageMainCallsAPI. + AttachServiceToDeploymentStage(context.Background(), currentStageId, service.GetServiceId()). + AttachServiceToDeploymentStageRequest(req). + Execute() + + if err != nil { + utils.PrintlnError(err) + os.Exit(1) + panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + } + + utils.Println("Service \"" + serviceName + "\" is no longer skipped from environment-level deployments") + }, +} + +func init() { + environmentStageCmd.AddCommand(environmentStageUnskipCmd) + environmentStageUnskipCmd.Flags().StringVarP(&organizationName, "organization", "", "", "Organization Name") + environmentStageUnskipCmd.Flags().StringVarP(&projectName, "project", "", "", "Project Name") + environmentStageUnskipCmd.Flags().StringVarP(&environmentName, "environment", "", "", "Environment Name") + environmentStageUnskipCmd.Flags().StringVarP(&serviceName, "name", "n", "", "Service Name") + + _ = environmentStageUnskipCmd.MarkFlagRequired("name") +} From f5f903f8a10db3800e41e11abc91b3534a077d52 Mon Sep 17 00:00:00 2001 From: Fabien FLEUREAU Date: Mon, 23 Feb 2026 15:58:46 +0100 Subject: [PATCH 2/2] refactor(stage): replace CheckErr with existing CheckError, fix skipped-only stage message refactor(stage): use utils.CheckErr for error handling in skip/unskip/list --- cmd/environment_stage_list.go | 58 +++++++++------------------------ cmd/environment_stage_skip.go | 34 ++++--------------- cmd/environment_stage_unskip.go | 32 +++--------------- 3 files changed, 26 insertions(+), 98 deletions(-) diff --git a/cmd/environment_stage_list.go b/cmd/environment_stage_list.go index d1769752..25d221b7 100644 --- a/cmd/environment_stage_list.go +++ b/cmd/environment_stage_list.go @@ -3,7 +3,6 @@ package cmd import ( "context" "encoding/json" - "os" "strconv" "github.com/pterm/pterm" @@ -19,28 +18,14 @@ var environmentStageListCmd = &cobra.Command{ utils.Capture(cmd) tokenType, token, err := utils.GetAccessToken() - if err != nil { - utils.PrintlnError(err) - os.Exit(1) - panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 - } + utils.CheckError(err) client := utils.GetQoveryClient(tokenType, token) _, _, environmentId, err := getOrganizationProjectEnvironmentContextResourcesIds(client) - - if err != nil { - utils.PrintlnError(err) - os.Exit(1) - panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 - } + utils.CheckError(err) stages, _, err := client.DeploymentStageMainCallsAPI.ListEnvironmentDeploymentStage(context.Background(), environmentId).Execute() - - if err != nil { - utils.PrintlnError(err) - os.Exit(1) - panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 - } + utils.CheckError(err) if jsonFlag { utils.Println(getEnvironmentStageJsonOutput(*client, stages.GetResults())) @@ -68,11 +53,7 @@ var environmentStageListCmd = &cobra.Command{ utils.Println("") err = utils.PrintTable([]string{"Id", "Type", "Name", "Stage"}, skippedData) utils.Println("") - if err != nil { - utils.PrintlnError(err) - os.Exit(1) - panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 - } + utils.CheckError(err) } // Show each stage with only non-skipped services @@ -97,18 +78,17 @@ var environmentStageListCmd = &cobra.Command{ } if len(data) == 0 { - utils.Println("") + if len(stage.GetServices()) == 0 { + utils.Println("") + } else { + utils.Println("") + } } else { err = utils.PrintTable([]string{"Id", "Type", "Name"}, data) + utils.CheckError(err) } utils.Println("") - - if err != nil { - utils.PrintlnError(err) - os.Exit(1) - panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 - } } }, } @@ -130,13 +110,12 @@ func getEnvironmentStageJsonOutput(client qovery.APIClient, stages []qovery.Depl services = append(services, entry) if service.GetIsSkipped() { - skippedEntry := map[string]interface{}{ + skippedServices = append(skippedServices, map[string]interface{}{ "id": service.ServiceId, "type": service.ServiceType, "name": utils.GetServiceNameByIdAndType(&client, service.GetServiceId(), service.GetServiceType()), "stage": stage.Name, - } - skippedServices = append(skippedServices, skippedEntry) + }) } } @@ -149,18 +128,11 @@ func getEnvironmentStageJsonOutput(client qovery.APIClient, stages []qovery.Depl }) } - output := map[string]interface{}{ + j, err := json.Marshal(map[string]interface{}{ "skipped_services": skippedServices, "stages": results, - } - - j, err := json.Marshal(output) - - if err != nil { - utils.PrintlnError(err) - os.Exit(1) - panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 - } + }) + utils.CheckError(err) return string(j) } diff --git a/cmd/environment_stage_skip.go b/cmd/environment_stage_skip.go index 99f9f684..ce2f383b 100644 --- a/cmd/environment_stage_skip.go +++ b/cmd/environment_stage_skip.go @@ -3,7 +3,6 @@ package cmd import ( "context" "errors" - "os" "github.com/qovery/qovery-cli/utils" "github.com/qovery/qovery-client-go" @@ -13,33 +12,19 @@ import ( var environmentStageSkipCmd = &cobra.Command{ Use: "skip", Short: "Skip service from environment-level deployments", - Long: "Mark a service as skipped so it is excluded from environment-level bulk deployments while staying in its current stage. Use 'environment stage unskip' to reverse.", + Long: "Mark a service as skipped so it is excluded from environment-level bulk deployments while staying in its current stage. To reverse this, use 'environment stage unskip' or move the service to a different deployment stage, which automatically clears the skipped status.", Run: func(cmd *cobra.Command, args []string) { utils.Capture(cmd) tokenType, token, err := utils.GetAccessToken() - if err != nil { - utils.PrintlnError(err) - os.Exit(1) - panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 - } + utils.CheckError(err) client := utils.GetQoveryClient(tokenType, token) _, _, environmentId, err := getOrganizationProjectEnvironmentContextResourcesIds(client) - - if err != nil { - utils.PrintlnError(err) - os.Exit(1) - panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 - } + utils.CheckError(err) stages, _, err := client.DeploymentStageMainCallsAPI.ListEnvironmentDeploymentStage(context.Background(), environmentId).Execute() - - if err != nil { - utils.PrintlnError(err) - os.Exit(1) - panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 - } + utils.CheckError(err) var service *qovery.DeploymentStageServiceResponse var currentStageId string @@ -52,9 +37,7 @@ var environmentStageSkipCmd = &cobra.Command{ } if service == nil { - utils.PrintlnError(errors.New("service not found")) - os.Exit(1) - panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + utils.CheckError(errors.New("service not found")) } req := qovery.AttachServiceToDeploymentStageRequest{} @@ -64,12 +47,7 @@ var environmentStageSkipCmd = &cobra.Command{ AttachServiceToDeploymentStage(context.Background(), currentStageId, service.GetServiceId()). AttachServiceToDeploymentStageRequest(req). Execute() - - if err != nil { - utils.PrintlnError(err) - os.Exit(1) - panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 - } + utils.CheckError(err) utils.Println("Service \"" + serviceName + "\" is now skipped from environment-level deployments") }, diff --git a/cmd/environment_stage_unskip.go b/cmd/environment_stage_unskip.go index a9d82402..a875b269 100644 --- a/cmd/environment_stage_unskip.go +++ b/cmd/environment_stage_unskip.go @@ -3,7 +3,6 @@ package cmd import ( "context" "errors" - "os" "github.com/qovery/qovery-cli/utils" "github.com/qovery/qovery-client-go" @@ -18,28 +17,14 @@ var environmentStageUnskipCmd = &cobra.Command{ utils.Capture(cmd) tokenType, token, err := utils.GetAccessToken() - if err != nil { - utils.PrintlnError(err) - os.Exit(1) - panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 - } + utils.CheckError(err) client := utils.GetQoveryClient(tokenType, token) _, _, environmentId, err := getOrganizationProjectEnvironmentContextResourcesIds(client) - - if err != nil { - utils.PrintlnError(err) - os.Exit(1) - panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 - } + utils.CheckError(err) stages, _, err := client.DeploymentStageMainCallsAPI.ListEnvironmentDeploymentStage(context.Background(), environmentId).Execute() - - if err != nil { - utils.PrintlnError(err) - os.Exit(1) - panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 - } + utils.CheckError(err) var service *qovery.DeploymentStageServiceResponse var currentStageId string @@ -52,9 +37,7 @@ var environmentStageUnskipCmd = &cobra.Command{ } if service == nil { - utils.PrintlnError(errors.New("service not found")) - os.Exit(1) - panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 + utils.CheckError(errors.New("service not found")) } req := qovery.AttachServiceToDeploymentStageRequest{} @@ -64,12 +47,7 @@ var environmentStageUnskipCmd = &cobra.Command{ AttachServiceToDeploymentStage(context.Background(), currentStageId, service.GetServiceId()). AttachServiceToDeploymentStageRequest(req). Execute() - - if err != nil { - utils.PrintlnError(err) - os.Exit(1) - panic("unreachable") // staticcheck false positive: https://staticcheck.io/docs/checks#SA5011 - } + utils.CheckError(err) utils.Println("Service \"" + serviceName + "\" is no longer skipped from environment-level deployments") },