diff --git a/internal/cmd/ske/cluster/create/create.go b/internal/cmd/ske/cluster/create/create.go index af9ba2336..24ac7a8a2 100644 --- a/internal/cmd/ske/cluster/create/create.go +++ b/internal/cmd/ske/cluster/create/create.go @@ -14,13 +14,13 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" serviceEnablementClient "github.com/stackitcloud/stackit-cli/internal/pkg/services/service-enablement/client" serviceEnablementUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/service-enablement/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" skeUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/validation" "github.com/stackitcloud/stackit-sdk-go/services/ske" "github.com/stackitcloud/stackit-sdk-go/services/ske/wait" ) @@ -70,16 +70,15 @@ func NewCmd(params *params.CmdParams) *cobra.Command { return err } - // Configure API client - apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + projectLabel, err := validation.ValidateAndGetProjectLabel(ctx, params.Printer, params.CliVersion, cmd, model.ProjectId) if err != nil { return err } - projectLabel, err := projectname.GetProjectName(ctx, params.Printer, params.CliVersion, cmd) + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) if err != nil { - params.Printer.Debug(print.ErrorLevel, "get project name: %v", err) - projectLabel = model.ProjectId + return err } if !model.AssumeYes { @@ -159,9 +158,6 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu clusterName := inputArgs[0] globalFlags := globalflags.Parse(p, cmd) - if globalFlags.ProjectId == "" { - return nil, &errors.ProjectIdError{} - } payloadValue := flags.FlagToStringPointer(p, cmd, payloadFlag) var payload *ske.CreateOrUpdateClusterPayload diff --git a/internal/cmd/ske/cluster/delete/delete.go b/internal/cmd/ske/cluster/delete/delete.go index 12068d9e3..72f6d0ff4 100644 --- a/internal/cmd/ske/cluster/delete/delete.go +++ b/internal/cmd/ske/cluster/delete/delete.go @@ -6,12 +6,12 @@ import ( "github.com/stackitcloud/stackit-cli/internal/cmd/params" "github.com/stackitcloud/stackit-cli/internal/pkg/args" - "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" + "github.com/stackitcloud/stackit-cli/internal/pkg/validation" "github.com/spf13/cobra" "github.com/stackitcloud/stackit-sdk-go/services/ske" @@ -45,6 +45,11 @@ func NewCmd(params *params.CmdParams) *cobra.Command { return err } + _, err = validation.ValidateAndGetProjectLabel(ctx, params.Printer, params.CliVersion, cmd, model.ProjectId) + if err != nil { + return err + } + // Configure API client apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) if err != nil { @@ -92,9 +97,6 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu clusterName := inputArgs[0] globalFlags := globalflags.Parse(p, cmd) - if globalFlags.ProjectId == "" { - return nil, &errors.ProjectIdError{} - } model := inputModel{ GlobalFlagModel: globalFlags, diff --git a/internal/cmd/ske/cluster/describe/describe.go b/internal/cmd/ske/cluster/describe/describe.go index a983f442e..69008b8e9 100644 --- a/internal/cmd/ske/cluster/describe/describe.go +++ b/internal/cmd/ske/cluster/describe/describe.go @@ -10,13 +10,13 @@ import ( "github.com/spf13/cobra" "github.com/stackitcloud/stackit-cli/internal/cmd/params" "github.com/stackitcloud/stackit-cli/internal/pkg/args" - "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/validation" "github.com/stackitcloud/stackit-sdk-go/services/ske" ) @@ -49,6 +49,12 @@ func NewCmd(params *params.CmdParams) *cobra.Command { if err != nil { return err } + + _, err = validation.ValidateAndGetProjectLabel(ctx, params.Printer, params.CliVersion, cmd, model.ProjectId) + if err != nil { + return err + } + // Configure API client apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) if err != nil { @@ -72,9 +78,6 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu clusterName := inputArgs[0] globalFlags := globalflags.Parse(p, cmd) - if globalFlags.ProjectId == "" { - return nil, &errors.ProjectIdError{} - } model := inputModel{ GlobalFlagModel: globalFlags, diff --git a/internal/cmd/ske/cluster/list/list.go b/internal/cmd/ske/cluster/list/list.go index 8c357924a..1c5b5e028 100644 --- a/internal/cmd/ske/cluster/list/list.go +++ b/internal/cmd/ske/cluster/list/list.go @@ -13,12 +13,12 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/flags" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" serviceEnablementClient "github.com/stackitcloud/stackit-cli/internal/pkg/services/service-enablement/client" serviceEnablementUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/service-enablement/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/validation" "github.com/spf13/cobra" "github.com/stackitcloud/stackit-sdk-go/services/ske" @@ -57,6 +57,11 @@ func NewCmd(params *params.CmdParams) *cobra.Command { return err } + projectLabel, err := validation.ValidateAndGetProjectLabel(ctx, params.Printer, params.CliVersion, cmd, model.ProjectId) + if err != nil { + return err + } + // Configure API client apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) if err != nil { @@ -91,14 +96,6 @@ func NewCmd(params *params.CmdParams) *cobra.Command { clusters = clusters[:*model.Limit] } - projectLabel := model.ProjectId - if len(clusters) == 0 { - projectLabel, err = projectname.GetProjectName(ctx, params.Printer, params.CliVersion, cmd) - if err != nil { - params.Printer.Debug(print.ErrorLevel, "get project name: %v", err) - } - } - return outputResult(params.Printer, model.OutputFormat, projectLabel, clusters) }, } @@ -113,9 +110,6 @@ func configureFlags(cmd *cobra.Command) { func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { globalFlags := globalflags.Parse(p, cmd) - if globalFlags.ProjectId == "" { - return nil, &errors.ProjectIdError{} - } limit := flags.FlagToInt64Pointer(p, cmd, limitFlag) if limit != nil && *limit < 1 { diff --git a/internal/cmd/ske/describe/describe.go b/internal/cmd/ske/describe/describe.go index da717570e..4ebf285bd 100644 --- a/internal/cmd/ske/describe/describe.go +++ b/internal/cmd/ske/describe/describe.go @@ -9,7 +9,6 @@ import ( "github.com/spf13/cobra" "github.com/stackitcloud/stackit-cli/internal/cmd/params" "github.com/stackitcloud/stackit-cli/internal/pkg/args" - "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" @@ -17,6 +16,7 @@ import ( skeUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/service-enablement/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/tables" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/validation" "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement" ) @@ -41,6 +41,12 @@ func NewCmd(params *params.CmdParams) *cobra.Command { if err != nil { return err } + + _, err = validation.ValidateAndGetProjectLabel(ctx, params.Printer, params.CliVersion, cmd, model.ProjectId) + if err != nil { + return err + } + // Configure API client apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) if err != nil { @@ -62,9 +68,6 @@ func NewCmd(params *params.CmdParams) *cobra.Command { func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { globalFlags := globalflags.Parse(p, cmd) - if globalFlags.ProjectId == "" { - return nil, &errors.ProjectIdError{} - } model := inputModel{ GlobalFlagModel: globalFlags, diff --git a/internal/cmd/ske/disable/disable.go b/internal/cmd/ske/disable/disable.go index ea355ada1..46cecb176 100644 --- a/internal/cmd/ske/disable/disable.go +++ b/internal/cmd/ske/disable/disable.go @@ -6,14 +6,13 @@ import ( "github.com/stackitcloud/stackit-cli/internal/cmd/params" "github.com/stackitcloud/stackit-cli/internal/pkg/args" - "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" "github.com/stackitcloud/stackit-cli/internal/pkg/services/service-enablement/client" "github.com/stackitcloud/stackit-cli/internal/pkg/services/service-enablement/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" + "github.com/stackitcloud/stackit-cli/internal/pkg/validation" "github.com/spf13/cobra" "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement" @@ -42,16 +41,15 @@ func NewCmd(params *params.CmdParams) *cobra.Command { return err } - // Configure API client - apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + projectLabel, err := validation.ValidateAndGetProjectLabel(ctx, params.Printer, params.CliVersion, cmd, model.ProjectId) if err != nil { return err } - projectLabel, err := projectname.GetProjectName(ctx, params.Printer, params.CliVersion, cmd) + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) if err != nil { - params.Printer.Debug(print.ErrorLevel, "get project name: %v", err) - projectLabel = model.ProjectId + return err } if !model.AssumeYes { @@ -93,9 +91,6 @@ func NewCmd(params *params.CmdParams) *cobra.Command { func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { globalFlags := globalflags.Parse(p, cmd) - if globalFlags.ProjectId == "" { - return nil, &errors.ProjectIdError{} - } model := inputModel{ GlobalFlagModel: globalFlags, diff --git a/internal/cmd/ske/enable/enable.go b/internal/cmd/ske/enable/enable.go index 91431b5e3..6dd3c86eb 100644 --- a/internal/cmd/ske/enable/enable.go +++ b/internal/cmd/ske/enable/enable.go @@ -6,14 +6,13 @@ import ( "github.com/stackitcloud/stackit-cli/internal/cmd/params" "github.com/stackitcloud/stackit-cli/internal/pkg/args" - "github.com/stackitcloud/stackit-cli/internal/pkg/errors" "github.com/stackitcloud/stackit-cli/internal/pkg/examples" "github.com/stackitcloud/stackit-cli/internal/pkg/globalflags" "github.com/stackitcloud/stackit-cli/internal/pkg/print" - "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" "github.com/stackitcloud/stackit-cli/internal/pkg/services/service-enablement/client" "github.com/stackitcloud/stackit-cli/internal/pkg/services/service-enablement/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/spinner" + "github.com/stackitcloud/stackit-cli/internal/pkg/validation" "github.com/spf13/cobra" "github.com/stackitcloud/stackit-sdk-go/services/serviceenablement" @@ -42,16 +41,15 @@ func NewCmd(params *params.CmdParams) *cobra.Command { return err } - // Configure API client - apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) + projectLabel, err := validation.ValidateAndGetProjectLabel(ctx, params.Printer, params.CliVersion, cmd, model.ProjectId) if err != nil { return err } - projectLabel, err := projectname.GetProjectName(ctx, params.Printer, params.CliVersion, cmd) + // Configure API client + apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) if err != nil { - params.Printer.Debug(print.ErrorLevel, "get project name: %v", err) - projectLabel = model.ProjectId + return err } if !model.AssumeYes { @@ -93,9 +91,6 @@ func NewCmd(params *params.CmdParams) *cobra.Command { func parseInput(p *print.Printer, cmd *cobra.Command) (*inputModel, error) { globalFlags := globalflags.Parse(p, cmd) - if globalFlags.ProjectId == "" { - return nil, &errors.ProjectIdError{} - } model := inputModel{ GlobalFlagModel: globalFlags, diff --git a/internal/cmd/ske/kubeconfig/create/create.go b/internal/cmd/ske/kubeconfig/create/create.go index f420009f0..51eef66c2 100644 --- a/internal/cmd/ske/kubeconfig/create/create.go +++ b/internal/cmd/ske/kubeconfig/create/create.go @@ -16,6 +16,7 @@ import ( "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/client" skeUtils "github.com/stackitcloud/stackit-cli/internal/pkg/services/ske/utils" "github.com/stackitcloud/stackit-cli/internal/pkg/utils" + "github.com/stackitcloud/stackit-cli/internal/pkg/validation" "github.com/spf13/cobra" "github.com/stackitcloud/stackit-sdk-go/services/ske" @@ -83,6 +84,11 @@ func NewCmd(params *params.CmdParams) *cobra.Command { return err } + _, err = validation.ValidateAndGetProjectLabel(ctx, params.Printer, params.CliVersion, cmd, model.ProjectId) + if err != nil { + return err + } + // Configure API client apiClient, err := client.ConfigureClient(params.Printer, params.CliVersion) if err != nil { @@ -180,9 +186,6 @@ func parseInput(p *print.Printer, cmd *cobra.Command, inputArgs []string) (*inpu clusterName := inputArgs[0] globalFlags := globalflags.Parse(p, cmd) - if globalFlags.ProjectId == "" { - return nil, &errors.ProjectIdError{} - } expTime := flags.FlagToStringPointer(p, cmd, expirationFlag) diff --git a/internal/pkg/errors/errors.go b/internal/pkg/errors/errors.go index 9c83fb4f1..a9d065a8d 100644 --- a/internal/pkg/errors/errors.go +++ b/internal/pkg/errors/errors.go @@ -178,6 +178,11 @@ To list all profiles, run: $ stackit config profile list` FILE_ALREADY_EXISTS = `file %q already exists in the export path. Delete the existing file or define a different export path` + + PROJECT_NOT_FOUND = `the project %[2]q (ID: %[1]s) does not exist or you don't have access to it. + +To list all available projects, run: + $ stackit project list` ) type ServerNicAttachMissingNicIdError struct { @@ -499,3 +504,12 @@ type FileAlreadyExistsError struct { } func (e *FileAlreadyExistsError) Error() string { return fmt.Sprintf(FILE_ALREADY_EXISTS, e.Filename) } + +type ProjectNotFoundError struct { + ProjectId string + ProjectLabel string +} + +func (e *ProjectNotFoundError) Error() string { + return fmt.Sprintf(PROJECT_NOT_FOUND, e.ProjectId, e.ProjectLabel) +} diff --git a/internal/pkg/validation/project.go b/internal/pkg/validation/project.go new file mode 100644 index 000000000..324bbac8b --- /dev/null +++ b/internal/pkg/validation/project.go @@ -0,0 +1,54 @@ +package validation + +import ( + "context" + "fmt" + "net/http" + + "github.com/stackitcloud/stackit-cli/internal/pkg/errors" + "github.com/stackitcloud/stackit-cli/internal/pkg/print" + "github.com/stackitcloud/stackit-cli/internal/pkg/projectname" + "github.com/stackitcloud/stackit-cli/internal/pkg/services/resourcemanager/client" + + "github.com/spf13/cobra" + "github.com/stackitcloud/stackit-sdk-go/core/oapierror" +) + +// ValidateAndGetProjectLabel validates that the project ID is not empty, exists, and the user has access to it. +// It returns the project label (name or ID) for display purposes. +func ValidateAndGetProjectLabel(ctx context.Context, p *print.Printer, cliVersion string, cmd *cobra.Command, projectId string) (string, error) { + // Check if project ID is empty + if projectId == "" { + return "", &errors.ProjectIdError{} + } + + // Configure Resource Manager API client + apiClient, err := client.ConfigureClient(p, cliVersion) + if err != nil { + return "", fmt.Errorf("configure resource manager client: %w", err) + } + + // Try to get project details to validate existence and access + req := apiClient.GetProject(ctx, projectId) + resp, err := req.Execute() + if err != nil { + // Check for specific HTTP status codes + if httpErr, ok := err.(*oapierror.GenericOpenAPIError); ok { //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped + switch httpErr.StatusCode { + case http.StatusForbidden: + // Try to get project name for better error message + projectLabel := projectId + if projectName, nameErr := projectname.GetProjectName(ctx, p, cliVersion, cmd); nameErr == nil { + projectLabel = projectName + } + return "", &errors.ProjectNotFoundError{ProjectId: projectId, ProjectLabel: projectLabel} + case http.StatusUnauthorized: + return "", &errors.AuthError{} + } + } + return "", fmt.Errorf("validate project: %w", err) + } + + // Project exists and user has access, returning the project name + return *resp.Name, nil +}