diff --git a/internal/cmd/dns/record-set/create/create.go b/internal/cmd/dns/record-set/create/create.go index 8ac2c847..977b195f 100644 --- a/internal/cmd/dns/record-set/create/create.go +++ b/internal/cmd/dns/record-set/create/create.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/stackitcloud/stackit-cli/internal/pkg/confirm" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/services/dns/client" @@ -46,6 +47,14 @@ func NewCmd() *cobra.Command { return err } + if !model.AssumeYes { + prompt := "Are you sure you want to create a record-set?" + err = confirm.PromptForConfirmation(cmd, prompt) + if err != nil { + return err + } + } + // Configure API client apiClient, err := client.ConfigureClient(cmd) if err != nil { @@ -89,7 +98,7 @@ func configureFlags(cmd *cobra.Command) { } func parseFlags(cmd *cobra.Command) (*flagModel, error) { - globalFlags := globalflags.Parse() + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/dns/record-set/delete/delete.go b/internal/cmd/dns/record-set/delete/delete.go index 9cfd12d2..f808eda5 100644 --- a/internal/cmd/dns/record-set/delete/delete.go +++ b/internal/cmd/dns/record-set/delete/delete.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/stackitcloud/stackit-cli/internal/pkg/confirm" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/services/dns/client" @@ -38,6 +39,14 @@ func NewCmd() *cobra.Command { return err } + if !model.AssumeYes { + prompt := fmt.Sprintf("Are you sure you want to delete record-set %s? (This cannot be undone)", model.RecordSetId) + err = confirm.PromptForConfirmation(cmd, prompt) + if err != nil { + return err + } + } + // Configure API client apiClient, err := client.ConfigureClient(cmd) if err != nil { @@ -77,7 +86,7 @@ func configureFlags(cmd *cobra.Command) { } func parseFlags(cmd *cobra.Command) (*flagModel, error) { - globalFlags := globalflags.Parse() + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/dns/record-set/describe/describe.go b/internal/cmd/dns/record-set/describe/describe.go index b2cea8de..f2342dc4 100644 --- a/internal/cmd/dns/record-set/describe/describe.go +++ b/internal/cmd/dns/record-set/describe/describe.go @@ -69,7 +69,7 @@ func configureFlags(cmd *cobra.Command) { } func parseFlags(cmd *cobra.Command) (*flagModel, error) { - globalFlags := globalflags.Parse() + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/dns/record-set/list/list.go b/internal/cmd/dns/record-set/list/list.go index a46cfd50..ce99a2c6 100644 --- a/internal/cmd/dns/record-set/list/list.go +++ b/internal/cmd/dns/record-set/list/list.go @@ -87,7 +87,7 @@ func configureFlags(cmd *cobra.Command) { } func parseFlags(cmd *cobra.Command) (*flagModel, error) { - globalFlags := globalflags.Parse() + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/dns/record-set/update/update.go b/internal/cmd/dns/record-set/update/update.go index b4c30081..de0e3440 100644 --- a/internal/cmd/dns/record-set/update/update.go +++ b/internal/cmd/dns/record-set/update/update.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/stackitcloud/stackit-cli/internal/pkg/confirm" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/services/dns/client" @@ -46,6 +47,14 @@ func NewCmd() *cobra.Command { return err } + if !model.AssumeYes { + prompt := fmt.Sprintf("Are you sure you want to update record-set %s?", model.RecordSetId) + err = confirm.PromptForConfirmation(cmd, prompt) + if err != nil { + return err + } + } + // Configure API client apiClient, err := client.ConfigureClient(cmd) if err != nil { @@ -86,7 +95,7 @@ func configureFlags(cmd *cobra.Command) { } func parseFlags(cmd *cobra.Command) (*flagModel, error) { - globalFlags := globalflags.Parse() + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/dns/zone/create/create.go b/internal/cmd/dns/zone/create/create.go index 2bcf2de6..e2b0e603 100644 --- a/internal/cmd/dns/zone/create/create.go +++ b/internal/cmd/dns/zone/create/create.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/stackitcloud/stackit-cli/internal/pkg/confirm" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/services/dns/client" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" @@ -59,6 +60,16 @@ func NewCmd() *cobra.Command { return err } + if !model.AssumeYes { + prompt := "Are you sure you want to create a zone?" + err = confirm.PromptForConfirmation(cmd, prompt) + if err != nil { + return err + } + } + + cmd.OutOrStdout() + // Configure API client apiClient, err := client.ConfigureClient(cmd) if err != nil { @@ -107,7 +118,7 @@ func configureFlags(cmd *cobra.Command) { } func parseFlags(cmd *cobra.Command) (*flagModel, error) { - globalFlags := globalflags.Parse() + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/dns/zone/delete/delete.go b/internal/cmd/dns/zone/delete/delete.go index 3f40f76c..75bb7c68 100644 --- a/internal/cmd/dns/zone/delete/delete.go +++ b/internal/cmd/dns/zone/delete/delete.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/stackitcloud/stackit-cli/internal/pkg/confirm" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/services/dns/client" @@ -36,6 +37,14 @@ func NewCmd() *cobra.Command { return err } + if !model.AssumeYes { + prompt := fmt.Sprintf("Are you sure you want to delete zone %s? (This cannot be undone)", model.ZoneId) + err = confirm.PromptForConfirmation(cmd, prompt) + if err != nil { + return err + } + } + // Configure API client apiClient, err := client.ConfigureClient(cmd) if err != nil { @@ -74,7 +83,7 @@ func configureFlags(cmd *cobra.Command) { } func parseFlags(cmd *cobra.Command) (*flagModel, error) { - globalFlags := globalflags.Parse() + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/dns/zone/describe/describe.go b/internal/cmd/dns/zone/describe/describe.go index 82a4ad58..dd51a77d 100644 --- a/internal/cmd/dns/zone/describe/describe.go +++ b/internal/cmd/dns/zone/describe/describe.go @@ -65,7 +65,7 @@ func configureFlags(cmd *cobra.Command) { } func parseFlags(cmd *cobra.Command) (*flagModel, error) { - globalFlags := globalflags.Parse() + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/dns/zone/list/list.go b/internal/cmd/dns/zone/list/list.go index 492cad9c..e28739ae 100644 --- a/internal/cmd/dns/zone/list/list.go +++ b/internal/cmd/dns/zone/list/list.go @@ -81,7 +81,7 @@ func configureFlags(cmd *cobra.Command) { } func parseFlags(cmd *cobra.Command) (*flagModel, error) { - globalFlags := globalflags.Parse() + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/dns/zone/update/update.go b/internal/cmd/dns/zone/update/update.go index 78fbad9e..d42dd1b0 100644 --- a/internal/cmd/dns/zone/update/update.go +++ b/internal/cmd/dns/zone/update/update.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/stackitcloud/stackit-cli/internal/pkg/confirm" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/services/dns/client" @@ -56,6 +57,14 @@ func NewCmd() *cobra.Command { return err } + if !model.AssumeYes { + prompt := fmt.Sprintf("Are you sure you want to update zone %s?", model.ZoneId) + err = confirm.PromptForConfirmation(cmd, prompt) + if err != nil { + return err + } + } + // Configure API client apiClient, err := client.ConfigureClient(cmd) if err != nil { @@ -104,7 +113,7 @@ func configureFlags(cmd *cobra.Command) { } func parseFlags(cmd *cobra.Command) (*flagModel, error) { - globalFlags := globalflags.Parse() + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/postgresql/credential/create/create.go b/internal/cmd/postgresql/credential/create/create.go index ea70fcef..9e656908 100644 --- a/internal/cmd/postgresql/credential/create/create.go +++ b/internal/cmd/postgresql/credential/create/create.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/stackitcloud/stackit-cli/internal/pkg/confirm" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/services/postgresql/client" @@ -37,6 +38,14 @@ func NewCmd() *cobra.Command { return err } + if !model.AssumeYes { + prompt := "Are you sure you want to create a credential?" + err = confirm.PromptForConfirmation(cmd, prompt) + if err != nil { + return err + } + } + // Configure API client apiClient, err := client.ConfigureClient(cmd) if err != nil { @@ -72,7 +81,7 @@ func configureFlags(cmd *cobra.Command) { } func parseFlags(cmd *cobra.Command) (*flagModel, error) { - globalFlags := globalflags.Parse() + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/postgresql/credential/delete/delete.go b/internal/cmd/postgresql/credential/delete/delete.go index fc956777..8973d69a 100644 --- a/internal/cmd/postgresql/credential/delete/delete.go +++ b/internal/cmd/postgresql/credential/delete/delete.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/stackitcloud/stackit-cli/internal/pkg/confirm" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/services/postgresql/client" @@ -37,6 +38,14 @@ func NewCmd() *cobra.Command { return err } + if !model.AssumeYes { + prompt := fmt.Sprintf("Are you sure you want to delete credential %s? (This cannot be undone)", model.CredentialId) + err = confirm.PromptForConfirmation(cmd, prompt) + if err != nil { + return err + } + } + // Configure API client apiClient, err := client.ConfigureClient(cmd) if err != nil { @@ -69,7 +78,7 @@ func configureFlags(cmd *cobra.Command) { } func parseFlags(cmd *cobra.Command) (*flagModel, error) { - globalFlags := globalflags.Parse() + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/postgresql/credential/describe/describe.go b/internal/cmd/postgresql/credential/describe/describe.go index b5c34fc6..1e3bff6a 100644 --- a/internal/cmd/postgresql/credential/describe/describe.go +++ b/internal/cmd/postgresql/credential/describe/describe.go @@ -70,7 +70,7 @@ func configureFlags(cmd *cobra.Command) { } func parseFlags(cmd *cobra.Command) (*flagModel, error) { - globalFlags := globalflags.Parse() + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/postgresql/credential/list/list.go b/internal/cmd/postgresql/credential/list/list.go index cc0f176f..de4b5d49 100644 --- a/internal/cmd/postgresql/credential/list/list.go +++ b/internal/cmd/postgresql/credential/list/list.go @@ -77,7 +77,7 @@ func configureFlags(cmd *cobra.Command) { } func parseFlags(cmd *cobra.Command) (*flagModel, error) { - globalFlags := globalflags.Parse() + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/postgresql/instance/create/create.go b/internal/cmd/postgresql/instance/create/create.go index 80f307bc..e7d347db 100644 --- a/internal/cmd/postgresql/instance/create/create.go +++ b/internal/cmd/postgresql/instance/create/create.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + "github.com/stackitcloud/stackit-cli/internal/pkg/confirm" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/services/postgresql/client" @@ -61,6 +62,14 @@ func NewCmd() *cobra.Command { return err } + if !model.AssumeYes { + prompt := "Are you sure you want to create an instance?" + err = confirm.PromptForConfirmation(cmd, prompt) + if err != nil { + return err + } + } + // Configure API client apiClient, err := client.ConfigureClient(cmd) if err != nil { @@ -115,7 +124,7 @@ func configureFlags(cmd *cobra.Command) { } func parseFlags(cmd *cobra.Command) (*flagModel, error) { - globalFlags := globalflags.Parse() + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/postgresql/instance/delete/delete.go b/internal/cmd/postgresql/instance/delete/delete.go index 739207b1..aadb9755 100644 --- a/internal/cmd/postgresql/instance/delete/delete.go +++ b/internal/cmd/postgresql/instance/delete/delete.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/stackitcloud/stackit-cli/internal/pkg/confirm" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/services/postgresql/client" @@ -35,6 +36,15 @@ func NewCmd() *cobra.Command { if err != nil { return err } + + if !model.AssumeYes { + prompt := fmt.Sprintf("Are you sure you want to delete instance %s? (This cannot be undone)", model.InstanceId) + err = confirm.PromptForConfirmation(cmd, prompt) + if err != nil { + return err + } + } + // Configure API client apiClient, err := client.ConfigureClient(cmd) if err != nil { @@ -70,7 +80,7 @@ func configureFlags(cmd *cobra.Command) { } func parseFlags(cmd *cobra.Command) (*flagModel, error) { - globalFlags := globalflags.Parse() + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/postgresql/instance/describe/describe.go b/internal/cmd/postgresql/instance/describe/describe.go index 14919d6f..971f065e 100644 --- a/internal/cmd/postgresql/instance/describe/describe.go +++ b/internal/cmd/postgresql/instance/describe/describe.go @@ -64,7 +64,7 @@ func configureFlags(cmd *cobra.Command) { } func parseFlags(cmd *cobra.Command) (*flagModel, error) { - globalFlags := globalflags.Parse() + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/postgresql/instance/list/list.go b/internal/cmd/postgresql/instance/list/list.go index 485b8562..0e1e4d6f 100644 --- a/internal/cmd/postgresql/instance/list/list.go +++ b/internal/cmd/postgresql/instance/list/list.go @@ -72,7 +72,7 @@ func configureFlags(cmd *cobra.Command) { } func parseFlags(cmd *cobra.Command) (*flagModel, error) { - globalFlags := globalflags.Parse() + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/postgresql/instance/update/update.go b/internal/cmd/postgresql/instance/update/update.go index d6b64329..f3508649 100644 --- a/internal/cmd/postgresql/instance/update/update.go +++ b/internal/cmd/postgresql/instance/update/update.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + "github.com/stackitcloud/stackit-cli/internal/pkg/confirm" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/services/postgresql/client" @@ -62,6 +63,14 @@ func NewCmd() *cobra.Command { return err } + if !model.AssumeYes { + prompt := fmt.Sprintf("Are you sure you want to update instance %s?", model.InstanceId) + err = confirm.PromptForConfirmation(cmd, prompt) + if err != nil { + return err + } + } + // Configure API client apiClient, err := client.ConfigureClient(cmd) if err != nil { @@ -116,7 +125,7 @@ func configureFlags(cmd *cobra.Command) { } func parseFlags(cmd *cobra.Command) (*flagModel, error) { - globalFlags := globalflags.Parse() + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/postgresql/offering/list/list.go b/internal/cmd/postgresql/offering/list/list.go index 8f51c4fb..f296ed57 100644 --- a/internal/cmd/postgresql/offering/list/list.go +++ b/internal/cmd/postgresql/offering/list/list.go @@ -72,7 +72,7 @@ func configureFlags(cmd *cobra.Command) { } func parseFlags(cmd *cobra.Command) (*flagModel, error) { - globalFlags := globalflags.Parse() + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/ske/cluster/create/create.go b/internal/cmd/ske/cluster/create/create.go index 71e53209..6e2beb3b 100644 --- a/internal/cmd/ske/cluster/create/create.go +++ b/internal/cmd/ske/cluster/create/create.go @@ -7,6 +7,7 @@ import ( "os" "strings" + "github.com/stackitcloud/stackit-cli/internal/pkg/confirm" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" skeUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/utils" @@ -41,6 +42,14 @@ func NewCmd() *cobra.Command { return err } + if !model.AssumeYes { + prompt := "Are you sure you want to create a cluster?" + err = confirm.PromptForConfirmation(cmd, prompt) + if err != nil { + return err + } + } + // Configure API client apiClient, err := client.ConfigureClient(cmd) if err != nil { @@ -92,7 +101,7 @@ func ConfigureFlags(cmd *cobra.Command) { type FileReaderFunc func(filename string) ([]byte, error) func ParseFlags(cmd *cobra.Command, fileReaderFunc FileReaderFunc) (*FlagModel, error) { - globalFlags := globalflags.Parse() + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/ske/cluster/delete/delete.go b/internal/cmd/ske/cluster/delete/delete.go index eb67864f..36a8311d 100644 --- a/internal/cmd/ske/cluster/delete/delete.go +++ b/internal/cmd/ske/cluster/delete/delete.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/stackitcloud/stackit-cli/internal/pkg/confirm" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" @@ -34,6 +35,15 @@ func NewCmd() *cobra.Command { if err != nil { return err } + + if !model.AssumeYes { + prompt := fmt.Sprintf("Are you sure you want to delete cluster %s? (This cannot be undone)", model.ClusterName) + err = confirm.PromptForConfirmation(cmd, prompt) + if err != nil { + return err + } + } + // Configure API client apiClient, err := client.ConfigureClient(cmd) if err != nil { @@ -69,7 +79,7 @@ func configureFlags(cmd *cobra.Command) { } func parseFlags(cmd *cobra.Command) (*flagModel, error) { - globalFlags := globalflags.Parse() + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/ske/cluster/describe/describe.go b/internal/cmd/ske/cluster/describe/describe.go index ce6b1dfe..af7910f0 100644 --- a/internal/cmd/ske/cluster/describe/describe.go +++ b/internal/cmd/ske/cluster/describe/describe.go @@ -62,7 +62,7 @@ func configureFlags(cmd *cobra.Command) { } func parseFlags(cmd *cobra.Command) (*flagModel, error) { - globalFlags := globalflags.Parse() + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/ske/cluster/list/list.go b/internal/cmd/ske/cluster/list/list.go index fac7a351..092e122d 100644 --- a/internal/cmd/ske/cluster/list/list.go +++ b/internal/cmd/ske/cluster/list/list.go @@ -72,7 +72,7 @@ func configureFlags(cmd *cobra.Command) { } func parseFlags(cmd *cobra.Command) (*flagModel, error) { - globalFlags := globalflags.Parse() + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/ske/cluster/update/update.go b/internal/cmd/ske/cluster/update/update.go index 115bb522..616696c0 100644 --- a/internal/cmd/ske/cluster/update/update.go +++ b/internal/cmd/ske/cluster/update/update.go @@ -6,6 +6,7 @@ import ( "os" "github.com/stackitcloud/stackit-cli/internal/cmd/ske/cluster/create" + "github.com/stackitcloud/stackit-cli/internal/pkg/confirm" "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/utils" @@ -26,6 +27,14 @@ func NewCmd() *cobra.Command { return err } + if !model.AssumeYes { + prompt := fmt.Sprintf("Are you sure you want to update cluster %s?", model.Name) + err = confirm.PromptForConfirmation(cmd, prompt) + if err != nil { + return err + } + } + // Configure API client apiClient, err := client.ConfigureClient(cmd) if err != nil { diff --git a/internal/cmd/ske/describe/describe.go b/internal/cmd/ske/describe/describe.go index f2186fce..a2d40228 100644 --- a/internal/cmd/ske/describe/describe.go +++ b/internal/cmd/ske/describe/describe.go @@ -25,7 +25,7 @@ func NewCmd() *cobra.Command { Example: `$ stackit ske describe --project-id xxx`, RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() - model, err := parseFlags() + model, err := parseFlags(cmd) if err != nil { return err } @@ -48,8 +48,8 @@ func NewCmd() *cobra.Command { return cmd } -func parseFlags() (*flagModel, error) { - globalFlags := globalflags.Parse() +func parseFlags(cmd *cobra.Command) (*flagModel, error) { + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/ske/describe/describe_test.go b/internal/cmd/ske/describe/describe_test.go index 67fdaed3..f5cb6b94 100644 --- a/internal/cmd/ske/describe/describe_test.go +++ b/internal/cmd/ske/describe/describe_test.go @@ -118,7 +118,7 @@ func TestParseFlags(t *testing.T) { t.Fatalf("error validating flags: %v", err) } - model, err := parseFlags() + model, err := parseFlags(cmd) if err != nil { if !tt.isValid { return diff --git a/internal/cmd/ske/disable/disable.go b/internal/cmd/ske/disable/disable.go index b45ec38d..a82ac61c 100644 --- a/internal/cmd/ske/disable/disable.go +++ b/internal/cmd/ske/disable/disable.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/stackitcloud/stackit-cli/internal/pkg/confirm" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" @@ -24,11 +25,19 @@ func NewCmd() *cobra.Command { Example: `$ stackit ske disable --project-id xxx`, RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() - model, err := parseFlags() + model, err := parseFlags(cmd) if err != nil { return err } + if !model.AssumeYes { + prompt := fmt.Sprintf("Are you sure you want to disable SKE for project %s? (This will delete all associated clusters)", model.ProjectId) + err = confirm.PromptForConfirmation(cmd, prompt) + if err != nil { + return err + } + } + // Configure API client apiClient, err := client.ConfigureClient(cmd) if err != nil { @@ -55,8 +64,8 @@ func NewCmd() *cobra.Command { return cmd } -func parseFlags() (*FlagModel, error) { - globalFlags := globalflags.Parse() +func parseFlags(cmd *cobra.Command) (*FlagModel, error) { + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/ske/disable/disable_test.go b/internal/cmd/ske/disable/disable_test.go index 93c59db4..df3ae680 100644 --- a/internal/cmd/ske/disable/disable_test.go +++ b/internal/cmd/ske/disable/disable_test.go @@ -118,7 +118,7 @@ func TestParseFlags(t *testing.T) { t.Fatalf("error validating flags: %v", err) } - model, err := parseFlags() + model, err := parseFlags(cmd) if err != nil { if !tt.isValid { return diff --git a/internal/cmd/ske/enable/enable.go b/internal/cmd/ske/enable/enable.go index 88756792..b13adb96 100644 --- a/internal/cmd/ske/enable/enable.go +++ b/internal/cmd/ske/enable/enable.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/stackitcloud/stackit-cli/internal/pkg/confirm" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" @@ -24,11 +25,19 @@ func NewCmd() *cobra.Command { Example: `$ stackit ske enable --project-id xxx`, RunE: func(cmd *cobra.Command, args []string) error { ctx := context.Background() - model, err := parseFlags() + model, err := parseFlags(cmd) if err != nil { return err } + if !model.AssumeYes { + prompt := fmt.Sprintf("Are you sure you want to enable SKE for project %s?", model.ProjectId) + err = confirm.PromptForConfirmation(cmd, prompt) + if err != nil { + return err + } + } + // Configure API client apiClient, err := client.ConfigureClient(cmd) if err != nil { @@ -55,8 +64,8 @@ func NewCmd() *cobra.Command { return cmd } -func parseFlags() (*FlagModel, error) { - globalFlags := globalflags.Parse() +func parseFlags(cmd *cobra.Command) (*FlagModel, error) { + globalFlags := globalflags.Parse(cmd) if globalFlags.ProjectId == "" { return nil, fmt.Errorf("project ID not set") } diff --git a/internal/cmd/ske/enable/enable_test.go b/internal/cmd/ske/enable/enable_test.go index f6f5719f..ce7e4368 100644 --- a/internal/cmd/ske/enable/enable_test.go +++ b/internal/cmd/ske/enable/enable_test.go @@ -118,7 +118,7 @@ func TestParseFlags(t *testing.T) { t.Fatalf("error validating flags: %v", err) } - model, err := parseFlags() + model, err := parseFlags(cmd) if err != nil { if !tt.isValid { return diff --git a/internal/pkg/confirm/confirm.go b/internal/pkg/confirm/confirm.go new file mode 100644 index 00000000..99a2f2f9 --- /dev/null +++ b/internal/pkg/confirm/confirm.go @@ -0,0 +1,36 @@ +package confirm + +import ( + "bufio" + "errors" + "fmt" + "strings" + + "github.com/spf13/cobra" +) + +var errAborted = errors.New("operation aborted") + +// Prompts the user for confirmation. +// +// Returns nil only if the user (explicitly) answers positive. +// Returns ErrAborted if the user answers negative. +func PromptForConfirmation(cmd *cobra.Command, prompt string) error { + question := fmt.Sprintf("%s [y/N] ", prompt) + reader := bufio.NewReader(cmd.InOrStdin()) + for i := 0; i < 3; i++ { + cmd.Print(question) + answer, err := reader.ReadString('\n') + if err != nil { + continue + } + answer = strings.ToLower(strings.TrimSpace(answer)) + if answer == "y" || answer == "yes" { + return nil + } + if answer == "" || answer == "n" || answer == "no" { + return errAborted + } + } + return fmt.Errorf("max number of wrong inputs") +} diff --git a/internal/pkg/confirm/confirm_test.go b/internal/pkg/confirm/confirm_test.go new file mode 100644 index 00000000..84f79295 --- /dev/null +++ b/internal/pkg/confirm/confirm_test.go @@ -0,0 +1,165 @@ +package confirm + +import ( + "bytes" + "errors" + "io" + "testing" + + "github.com/spf13/cobra" +) + +func TestPromptForConfirmation(t *testing.T) { + tests := []struct { + description string + input string + isValid bool + isAborted bool + }{ + // Note: Some of these inputs have normal spaces, others have tabs + { + description: "yes - simple 1", + input: "y\n", + isValid: true, + }, + { + description: "yes - simple 2", + input: " Y \r\n", + isValid: true, + }, + { + description: "yes - simple 3", + input: " yes\n", + isValid: true, + }, + { + description: "yes - simple 4", + input: "YES\n", + isValid: true, + }, + { + description: "yes - retries 1", + input: "yrs\nyes\n", + isValid: true, + }, + { + description: "yes - retries 2", + input: "foo\nbar \n y\n", + isValid: true, + }, + { + description: "yes - retries 3", + input: "foo\r\nbar \nY \n", + isValid: true, + }, + { + description: "no - simple 1", + input: "n\n", + isValid: false, + isAborted: true, + }, + { + description: "no - simple 2", + input: " N \r\n", + isValid: false, + isAborted: true, + }, + { + description: "no - simple 3", + input: "no\n", + isValid: false, + isAborted: true, + }, + { + description: "no - simple 4", + input: " \n", + isValid: false, + isAborted: true, + }, + { + description: "no - simple 5", + input: " \r\n", + isValid: false, + isAborted: true, + }, + { + description: "no - retries 1", + input: "ni\n no \n", + isValid: false, + isAborted: true, + }, + { + description: "no - retries 2", + input: "foo\nbar\nn\n", + isValid: false, + isAborted: true, + }, + { + description: "no - retries 3", + input: "foo\r\nbar\nN\n", + isValid: false, + isAborted: true, + }, + { + description: "no - retries 4", + input: "m\n \n", + isValid: false, + isAborted: true, + }, + { + description: "no - retries 5", + input: "m\r\n \r\n", + isValid: false, + isAborted: true, + }, + { + description: "max retries 1", + input: "foo\nbar\nbaz\n", + isValid: false, + }, + { + description: "max retries 2", + input: "foo\r\nbar\r\nbaz\r\n", + isValid: false, + }, + { + description: "max retries 3", + input: "foo\nbar\nbaz\ny\n", + isValid: false, + }, + { + description: "no input", + input: "", + isValid: false, + }, + } + + for _, tt := range tests { + t.Run(tt.description, func(t *testing.T) { + buffer := &bytes.Buffer{} + _, err := buffer.WriteString(tt.input) + if err != nil { + t.Fatalf("failed to initialize mock input: %v", err) + } + + cmd := &cobra.Command{} + cmd.SetOut(io.Discard) // Suppresses console prints + cmd.SetIn(buffer) + + err = PromptForConfirmation(cmd, "") + + if tt.isValid && err != nil { + t.Errorf("should not have failed: %v", err) + } + if !tt.isValid && err == nil { + t.Errorf("should have failed") + } + if tt.isAborted && !errors.Is(err, errAborted) { + t.Errorf("should have returned aborted error, instead returned: %v", err) + } + if !tt.isAborted && errors.Is(err, errAborted) { + t.Errorf("should not have returned aborted error") + } + }) + } +} diff --git a/internal/pkg/globalflags/global_flags.go b/internal/pkg/globalflags/global_flags.go index 48eb1916..46bc882a 100644 --- a/internal/pkg/globalflags/global_flags.go +++ b/internal/pkg/globalflags/global_flags.go @@ -3,10 +3,12 @@ package globalflags import ( "fmt" + "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" "github.com/stackitcloud/stackit-cli/internal/pkg/config" "github.com/stackitcloud/stackit-cli/internal/pkg/flags" + "github.com/stackitcloud/stackit-cli/internal/pkg/utils" ) const ( @@ -14,6 +16,7 @@ const ( OutputFormatFlag = "output-format" JSONOutputFormat = "json" TableOutputFormat = "table" + AssumeYesFlag = "assume-yes" ) var outputFormatFlagOptions = []string{JSONOutputFormat, TableOutputFormat} @@ -21,6 +24,7 @@ var outputFormatFlagOptions = []string{JSONOutputFormat, TableOutputFormat} type GlobalFlagModel struct { ProjectId string OutputFormat string + AssumeYes bool } func Configure(flagSet *pflag.FlagSet) error { @@ -35,12 +39,15 @@ func Configure(flagSet *pflag.FlagSet) error { if err != nil { return fmt.Errorf("bind --%s flag to config: %w", OutputFormatFlag, err) } + + flagSet.BoolP(AssumeYesFlag, "y", false, "If set, skips all confirmation prompts") return nil } -func Parse() *GlobalFlagModel { +func Parse(cmd *cobra.Command) *GlobalFlagModel { return &GlobalFlagModel{ ProjectId: viper.GetString(config.ProjectIdKey), OutputFormat: viper.GetString(config.OutputFormatKey), + AssumeYes: utils.FlagToBoolValue(cmd, AssumeYesFlag), } }