diff --git a/cmd/cluster/aws/destroy.go b/cmd/cluster/aws/destroy.go index 9bd6a2f141a5..707c48420644 100644 --- a/cmd/cluster/aws/destroy.go +++ b/cmd/cluster/aws/destroy.go @@ -157,8 +157,18 @@ func DestroyCluster(ctx context.Context, o *core.DestroyOptions) error { // ValidateCredentialInfo validates if the credentials secret name is empty, the aws-creds or sts-creds mutually exclusive and are not empty; validates if // the credentials secret is not empty, that it can be retrieved. func ValidateCredentialInfo(opts awsutil.AWSCredentialsOptions, credentialSecretName, namespace, kubeconfigPath string) error { + return validateCredentialInfo(opts, credentialSecretName, namespace, kubeconfigPath, opts.Validate) +} + +// ValidateProductCredentialInfo is like ValidateCredentialInfo but requires explicit --sts-creds and --role-arn +// flags rather than allowing SDK default chain fallback. +func ValidateProductCredentialInfo(opts awsutil.AWSCredentialsOptions, credentialSecretName, namespace, kubeconfigPath string) error { + return validateCredentialInfo(opts, credentialSecretName, namespace, kubeconfigPath, opts.ValidateProduct) +} + +func validateCredentialInfo(opts awsutil.AWSCredentialsOptions, credentialSecretName, namespace, kubeconfigPath string, validate func() error) error { if len(credentialSecretName) == 0 { - if err := opts.Validate(); err != nil { + if err := validate(); err != nil { return err } return nil diff --git a/cmd/cluster/aws/destroy_test.go b/cmd/cluster/aws/destroy_test.go index 4f3b9a972ef8..0cdfd33b9fba 100644 --- a/cmd/cluster/aws/destroy_test.go +++ b/cmd/cluster/aws/destroy_test.go @@ -14,7 +14,7 @@ func Test_ValidateCredentialInfo(t *testing.T) { inputOptions *core.DestroyOptions expectError bool }{ - "when CredentialSecretName is blank and aws-creds is also blank": { + "when CredentialSecretName is blank and aws-creds is also blank it should fall back to SDK default chain": { inputOptions: &core.DestroyOptions{ CredentialSecretName: "", AWSPlatform: core.AWSPlatformDestroyOptions{ @@ -23,7 +23,7 @@ func Test_ValidateCredentialInfo(t *testing.T) { }, }, }, - expectError: true, + expectError: false, }, "when CredentialSecretName is blank and aws-creds is not blank": { inputOptions: &core.DestroyOptions{ diff --git a/cmd/infra/aws/create_cli_role.go b/cmd/infra/aws/create_cli_role.go index 78d402c3c851..22a7d73b0072 100644 --- a/cmd/infra/aws/create_cli_role.go +++ b/cmd/infra/aws/create_cli_role.go @@ -175,8 +175,6 @@ func NewCreateCLIRoleCommand() *cobra.Command { cmd.Flags().StringVar(&opts.RoleName, "name", opts.RoleName, "Role name") cmd.Flags().StringToStringVarP(&opts.AdditionalTags, "additional-tags", "t", opts.AdditionalTags, "Additional tags to apply to the role created (e.g. 'key1=value1,key2=value2')") - _ = cmd.MarkFlagRequired("aws-creds") - logger := log.Log cmd.RunE = func(cmd *cobra.Command, args []string) error { if err := opts.Run(cmd.Context(), logger); err != nil { diff --git a/cmd/infra/aws/create_operator_roles.go b/cmd/infra/aws/create_operator_roles.go index cd832f422ee0..99f4c9f2b79a 100644 --- a/cmd/infra/aws/create_operator_roles.go +++ b/cmd/infra/aws/create_operator_roles.go @@ -126,18 +126,12 @@ func (o *CreateOperatorRolesOptions) Run(ctx context.Context, logger logr.Logger return err } - var awsSession *aws.Config - if o.AWSCredentialsOpts.AWSCredentialsFile != "" || o.AWSCredentialsOpts.STSCredentialsFile != "" { - if err := o.AWSCredentialsOpts.Validate(); err != nil { - return err - } - var err error - awsSession, err = o.AWSCredentialsOpts.GetSession(ctx, "cli-create-operator-roles", nil, o.Region) - if err != nil { - return err - } - } else { - awsSession = awsutil.NewSession(ctx, "cli-create-operator-roles", "", "", "", o.Region) + if err := o.AWSCredentialsOpts.Validate(); err != nil { + return err + } + awsSession, err := o.AWSCredentialsOpts.GetSession(ctx, "cli-create-operator-roles", nil, o.Region) + if err != nil { + return err } awsConfig := awsutil.NewConfig() iamClient := iam.NewFromConfig(*awsSession, func(o *iam.Options) { diff --git a/cmd/infra/aws/destroy.go b/cmd/infra/aws/destroy.go index 4a3b7d2a1c23..871abcc707bc 100644 --- a/cmd/infra/aws/destroy.go +++ b/cmd/infra/aws/destroy.go @@ -101,20 +101,17 @@ func (o *DelegatedAWSCredentialOptions) Validate() error { } } - // ensure that only one type of credential has been passed - globalCredentialsPresent := o.AWSCredentialsOpts.AWSCredentialsFile != "" || o.AWSCredentialsOpts.STSCredentialsFile != "" - if globalCredentialsPresent { - if !anyComponentCredentialsPresent { - return o.AWSCredentialsOpts.Validate() - } else { + if anyComponentCredentialsPresent { + if o.AWSCredentialsOpts.AWSCredentialsFile != "" || o.AWSCredentialsOpts.STSCredentialsFile != "" || o.AWSCredentialsOpts.RoleArn != "" { return fmt.Errorf("cannot set any --aws-creds.component flags at the same time as other credentials") } - } else { if !allComponentCredentialsPresent { - return fmt.Errorf("either --aws-creds, --sts-creds, or all --aws-creds.component flags must be set") + return fmt.Errorf("all --aws-creds.component flags must be set when using per-component credentials") } + return nil } - return nil + + return o.AWSCredentialsOpts.Validate() } func NewDestroyCommand() *cobra.Command { @@ -199,7 +196,13 @@ func (o *DestroyInfraOptions) DestroyInfra(ctx context.Context) error { var clusterRoute53Client, vpcOwnerRoute53Client, listRoute53Client, recordsRoute53Client awsapi.ROUTE53API var s3Client awsapi.S3API var ramClient *ram.Client - if o.AWSCredentialsOpts.AWSCredentialsOpts.AWSCredentialsFile != "" || o.AWSCredentialsOpts.AWSCredentialsOpts.STSCredentialsFile != "" { + useDelegatedCredentials := o.AWSEbsCsiDriverControllerCredentialsFile != "" || + o.CloudControllerCredentialsFile != "" || + o.CloudNetworkConfigControllerCredentialsFile != "" || + o.ControlPlaneOperatorCredentialsFile != "" || + o.NodePoolCredentialsFile != "" || + o.OpenshiftImageRegistryCredentialsFile != "" + if !useDelegatedCredentials { awsSession, err := o.AWSCredentialsOpts.AWSCredentialsOpts.GetSession(ctx, "cli-destroy-infra", o.CredentialsSecretData, o.Region) if err != nil { return err @@ -265,6 +268,7 @@ func (o *DestroyInfraOptions) DestroyInfra(ctx context.Context) error { return fmt.Errorf("failed to create delegating client: %w", err) } ec2Client = delegatingClent.EC2Client + vpcOwnerEC2Client = ec2Client elbClient = delegatingClent.ELBClient elbv2Client = delegatingClent.ELBV2Client listRoute53Client = delegatingClent.ROUTE53Client diff --git a/cmd/infra/aws/destroy_test.go b/cmd/infra/aws/destroy_test.go index dd91a5361abd..c8564f3c882f 100644 --- a/cmd/infra/aws/destroy_test.go +++ b/cmd/infra/aws/destroy_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + awsutil "github.com/openshift/hypershift/cmd/infra/aws/util" "github.com/openshift/hypershift/support/awsapi" "github.com/aws/aws-sdk-go-v2/aws" @@ -21,6 +22,107 @@ import ( "go.uber.org/mock/gomock" ) +func TestDelegatedAWSCredentialOptionsValidate(t *testing.T) { + allComponentCreds := DelegatedAWSCredentialOptions{ + AWSCredentialsOpts: &awsutil.AWSCredentialsOptions{}, + AWSEbsCsiDriverControllerCredentialsFile: "/tmp/ebs.ini", + CloudControllerCredentialsFile: "/tmp/cloud.ini", + CloudNetworkConfigControllerCredentialsFile: "/tmp/net.ini", + ControlPlaneOperatorCredentialsFile: "/tmp/cpo.ini", + NodePoolCredentialsFile: "/tmp/np.ini", + OpenshiftImageRegistryCredentialsFile: "/tmp/registry.ini", + } + + testCases := []struct { + name string + opts DelegatedAWSCredentialOptions + expectError bool + errorContains string + }{ + { + name: "When all component credentials provided, it should succeed", + opts: allComponentCreds, + expectError: false, + }, + { + name: "When component credentials mixed with aws-creds, it should fail", + opts: DelegatedAWSCredentialOptions{ + AWSCredentialsOpts: &awsutil.AWSCredentialsOptions{ + AWSCredentialsFile: "/tmp/global.ini", + }, + AWSEbsCsiDriverControllerCredentialsFile: "/tmp/ebs.ini", + CloudControllerCredentialsFile: "/tmp/cloud.ini", + CloudNetworkConfigControllerCredentialsFile: "/tmp/net.ini", + ControlPlaneOperatorCredentialsFile: "/tmp/cpo.ini", + NodePoolCredentialsFile: "/tmp/np.ini", + OpenshiftImageRegistryCredentialsFile: "/tmp/registry.ini", + }, + expectError: true, + errorContains: "cannot set any --aws-creds.component flags at the same time as other credentials", + }, + { + name: "When component credentials mixed with role-arn, it should fail", + opts: DelegatedAWSCredentialOptions{ + AWSCredentialsOpts: &awsutil.AWSCredentialsOptions{ + RoleArn: "arn:aws:iam::123456789012:role/test-role", + }, + AWSEbsCsiDriverControllerCredentialsFile: "/tmp/ebs.ini", + CloudControllerCredentialsFile: "/tmp/cloud.ini", + CloudNetworkConfigControllerCredentialsFile: "/tmp/net.ini", + ControlPlaneOperatorCredentialsFile: "/tmp/cpo.ini", + NodePoolCredentialsFile: "/tmp/np.ini", + OpenshiftImageRegistryCredentialsFile: "/tmp/registry.ini", + }, + expectError: true, + errorContains: "cannot set any --aws-creds.component flags at the same time as other credentials", + }, + { + name: "When partial component credentials provided, it should fail", + opts: DelegatedAWSCredentialOptions{ + AWSCredentialsOpts: &awsutil.AWSCredentialsOptions{}, + AWSEbsCsiDriverControllerCredentialsFile: "/tmp/ebs.ini", + CloudControllerCredentialsFile: "/tmp/cloud.ini", + CloudNetworkConfigControllerCredentialsFile: "/tmp/net.ini", + }, + expectError: true, + errorContains: "all --aws-creds.component flags must be set when using per-component credentials", + }, + { + name: "When no component credentials provided, it should fall back to AWSCredentialsOpts.Validate", + opts: DelegatedAWSCredentialOptions{ + AWSCredentialsOpts: &awsutil.AWSCredentialsOptions{}, + }, + expectError: false, + }, + { + name: "When no component credentials and invalid AWSCredentialsOpts, it should propagate validation error", + opts: DelegatedAWSCredentialOptions{ + AWSCredentialsOpts: &awsutil.AWSCredentialsOptions{ + STSCredentialsFile: "/tmp/creds.json", + }, + }, + expectError: true, + errorContains: "'role-arn' is required when 'sts-creds' is provided", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.opts.Validate() + if tc.expectError { + if err == nil { + t.Fatal("Expected error but got nil") + } + if tc.errorContains != "" && !strings.Contains(err.Error(), tc.errorContains) { + t.Errorf("Expected error containing %q, got %q", tc.errorContains, err.Error()) + } + } else if err != nil { + t.Errorf("Unexpected error: %v", err) + } + }) + } +} + func TestEmptyBucket(t *testing.T) { tests := []struct { name string diff --git a/cmd/infra/aws/util/util.go b/cmd/infra/aws/util/util.go index bdb9646d7632..a64d2d72502e 100644 --- a/cmd/infra/aws/util/util.go +++ b/cmd/infra/aws/util/util.go @@ -2,13 +2,13 @@ package util import ( "context" - "errors" "fmt" "net" "os" "time" "github.com/openshift/hypershift/cmd/util" + supportawsutil "github.com/openshift/hypershift/support/awsutil" "github.com/aws/aws-sdk-go-v2/aws" awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware" @@ -20,6 +20,8 @@ import ( "github.com/spf13/pflag" ) +var assumeRoleFn = supportawsutil.AssumeRole + type AWSCredentialsOptions struct { AWSCredentialsFile string @@ -32,16 +34,23 @@ func (opts *AWSCredentialsOptions) Validate() error { if opts.STSCredentialsFile != "" || opts.RoleArn != "" { return fmt.Errorf("only one of 'aws-creds' or 'role-arn' and 'sts-creds' can be provided") } - return nil } - if err := util.ValidateRequiredOption("sts-creds", opts.STSCredentialsFile); err != nil { - return err + if opts.STSCredentialsFile != "" && opts.RoleArn == "" { + return fmt.Errorf("'role-arn' is required when 'sts-creds' is provided") } - if err := util.ValidateRequiredOption("role-arn", opts.RoleArn); err != nil { + + return nil +} + +func (opts *AWSCredentialsOptions) ValidateProduct() error { + if err := opts.Validate(); err != nil { return err } + if opts.STSCredentialsFile == "" || opts.RoleArn == "" { + return fmt.Errorf("'sts-creds' and 'role-arn' are required") + } return nil } @@ -49,7 +58,7 @@ func (opts *AWSCredentialsOptions) BindFlags(flags *pflag.FlagSet) { opts.BindProductFlags(flags) flags.StringVar(&opts.AWSCredentialsFile, "aws-creds", opts.AWSCredentialsFile, "Path to an AWS credentials file") - _ = flags.MarkDeprecated("aws-creds", "please use '--sts-creds' with '--role-arn' instead") + _ = flags.MarkDeprecated("aws-creds", "the AWS SDK default credential chain is used when no explicit credentials are provided; use '--role-arn' to assume a specific role") } func (opts *AWSCredentialsOptions) BindVPCOwnerFlags(flags *pflag.FlagSet) { @@ -86,7 +95,30 @@ func (opts *AWSCredentialsOptions) GetSession(ctx context.Context, agent string, return NewSTSSession(ctx, agent, opts.RoleArn, region, &v2Creds) } - return nil, errors.New("could not create AWS session, no credentials were given") + // Fall back to the SDK default credential chain (env vars, AWS_PROFILE, instance profile, etc.) + cfg := NewSession(ctx, agent, "", "", "", region) + if opts.RoleArn != "" { + assumedCreds, err := assumeRoleFn(ctx, *cfg, agent, opts.RoleArn) + if err != nil { + return nil, err + } + var loadOptions []func(*config.LoadOptions) error + loadOptions = append(loadOptions, config.WithCredentialsProvider( + credentials.StaticCredentialsProvider{Value: *assumedCreds}, + )) + if region != "" { + loadOptions = append(loadOptions, config.WithRegion(region)) + } + loadOptions = append(loadOptions, config.WithAPIOptions([]func(*middleware.Stack) error{ + awsmiddleware.AddUserAgentKeyValue("openshift.io hypershift", agent), + })) + finalCfg, err := config.LoadDefaultConfig(ctx, loadOptions...) + if err != nil { + return nil, fmt.Errorf("failed to load config after assuming role: %w", err) + } + return &finalCfg, nil + } + return cfg, nil } func NewSession(ctx context.Context, agent, credentialsFile, credKey, credSecretKey, region string) *aws.Config { diff --git a/cmd/infra/aws/util/util_test.go b/cmd/infra/aws/util/util_test.go index 50d5607b213a..72a696d08009 100644 --- a/cmd/infra/aws/util/util_test.go +++ b/cmd/infra/aws/util/util_test.go @@ -6,6 +6,8 @@ import ( "path/filepath" "testing" + "github.com/openshift/hypershift/cmd/util" + "github.com/aws/aws-sdk-go-v2/aws" ) @@ -216,23 +218,55 @@ aws_secret_access_key = test-secret-key expectError: false, }, { - name: "When no credentials provided, it should return error", + name: "When no credentials provided, it should fall back to SDK default chain", opts: AWSCredentialsOptions{}, agent: "test-agent", region: "us-east-1", + expectError: false, + }, + { + name: "When RoleArn set without credentials files, it should attempt role assumption via default chain and fail", + opts: AWSCredentialsOptions{RoleArn: "arn:aws:iam::123456789012:role/test-role"}, + agent: "test-agent", + region: "us-east-1", + expectError: true, + }, + { + name: "When STS credentials file does not exist, it should return error", + opts: AWSCredentialsOptions{STSCredentialsFile: "/nonexistent/creds.json", RoleArn: "arn:aws:iam::123456789012:role/test-role"}, + agent: "test-agent", + region: "us-east-1", + expectError: true, + }, + { + name: "When secret data provided with RoleArn, it should attempt STS session and fail", + opts: AWSCredentialsOptions{RoleArn: "arn:aws:iam::123456789012:role/test-role"}, + setupFunc: func(t *testing.T) string { + return "use-secret-data" + }, + agent: "test-agent", + region: "us-east-1", expectError: true, - errorMsg: "could not create AWS session, no credentials were given", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { + var secretData *util.CredentialsSecretData if tc.setupFunc != nil { - credsFile := tc.setupFunc(t) - tc.opts.AWSCredentialsFile = credsFile + result := tc.setupFunc(t) + if result == "use-secret-data" { + secretData = &util.CredentialsSecretData{ + AWSAccessKeyID: "test-access-key", + AWSSecretAccessKey: "test-secret-key", + AWSSessionToken: "test-session-token", + } + } else { + tc.opts.AWSCredentialsFile = result + } } - cfg, err := tc.opts.GetSession(ctx, tc.agent, nil, tc.region) + cfg, err := tc.opts.GetSession(ctx, tc.agent, secretData, tc.region) if tc.expectError { if err == nil { @@ -253,3 +287,171 @@ aws_secret_access_key = test-secret-key }) } } + +func TestGetSession_DefaultChainRoleAssumption(t *testing.T) { + original := assumeRoleFn + t.Cleanup(func() { assumeRoleFn = original }) + + testCases := []struct { + name string + opts AWSCredentialsOptions + region string + validateConfig func(t *testing.T, cfg *aws.Config) + }{ + { + name: "When RoleArn set with region, it should return config with assumed credentials and region", + opts: AWSCredentialsOptions{RoleArn: "arn:aws:iam::123456789012:role/test-role"}, + region: "us-west-2", + validateConfig: func(t *testing.T, cfg *aws.Config) { + if cfg.Region != "us-west-2" { + t.Errorf("expected region us-west-2, got %s", cfg.Region) + } + creds, err := cfg.Credentials.Retrieve(context.Background()) + if err != nil { + t.Fatalf("failed to retrieve credentials: %v", err) + } + if creds.AccessKeyID != "assumed-key" { + t.Errorf("expected AccessKeyID %q, got %q", "assumed-key", creds.AccessKeyID) + } + }, + }, + { + name: "When RoleArn set without region, it should return config with assumed credentials", + opts: AWSCredentialsOptions{RoleArn: "arn:aws:iam::123456789012:role/test-role"}, + region: "", + validateConfig: func(t *testing.T, cfg *aws.Config) { + creds, err := cfg.Credentials.Retrieve(context.Background()) + if err != nil { + t.Fatalf("failed to retrieve credentials: %v", err) + } + if creds.AccessKeyID != "assumed-key" { + t.Errorf("expected AccessKeyID %q, got %q", "assumed-key", creds.AccessKeyID) + } + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + assumeRoleFn = func(_ context.Context, _ aws.Config, _, _ string) (*aws.Credentials, error) { + return &aws.Credentials{ + AccessKeyID: "assumed-key", + SecretAccessKey: "assumed-secret", + SessionToken: "assumed-token", + }, nil + } + + cfg, err := tc.opts.GetSession(context.Background(), "test-agent", nil, tc.region) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if cfg == nil { + t.Fatal("expected non-nil config") + } + tc.validateConfig(t, cfg) + }) + } +} + +func TestValidate(t *testing.T) { + testCases := []struct { + name string + opts AWSCredentialsOptions + expectError bool + errorMsg string + }{ + { + name: "When no flags provided, it should succeed for SDK default chain", + opts: AWSCredentialsOptions{}, + expectError: false, + }, + { + name: "When only role-arn provided, it should succeed for default chain with role assumption", + opts: AWSCredentialsOptions{RoleArn: "arn:aws:iam::123456789012:role/test-role"}, + expectError: false, + }, + { + name: "When sts-creds and role-arn provided, it should succeed", + opts: AWSCredentialsOptions{STSCredentialsFile: "/tmp/creds.json", RoleArn: "arn:aws:iam::123456789012:role/test-role"}, + expectError: false, + }, + { + name: "When only sts-creds provided without role-arn, it should fail", + opts: AWSCredentialsOptions{STSCredentialsFile: "/tmp/creds.json"}, + expectError: true, + errorMsg: "'role-arn' is required when 'sts-creds' is provided", + }, + { + name: "When aws-creds combined with sts-creds, it should fail", + opts: AWSCredentialsOptions{AWSCredentialsFile: "/tmp/aws.ini", STSCredentialsFile: "/tmp/creds.json"}, + expectError: true, + }, + { + name: "When aws-creds combined with role-arn, it should fail", + opts: AWSCredentialsOptions{AWSCredentialsFile: "/tmp/aws.ini", RoleArn: "arn:aws:iam::123456789012:role/test-role"}, + expectError: true, + }, + { + name: "When only aws-creds provided, it should succeed", + opts: AWSCredentialsOptions{AWSCredentialsFile: "/tmp/aws.ini"}, + expectError: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.opts.Validate() + if tc.expectError { + if err == nil { + t.Error("Expected error but got nil") + } else if tc.errorMsg != "" && err.Error() != tc.errorMsg { + t.Errorf("Expected error message %q, got %q", tc.errorMsg, err.Error()) + } + } else if err != nil { + t.Errorf("Unexpected error: %v", err) + } + }) + } +} + +func TestValidateProduct(t *testing.T) { + testCases := []struct { + name string + opts AWSCredentialsOptions + expectError bool + }{ + { + name: "When no flags provided, it should fail", + opts: AWSCredentialsOptions{}, + expectError: true, + }, + { + name: "When only role-arn provided, it should fail", + opts: AWSCredentialsOptions{RoleArn: "arn:aws:iam::123456789012:role/test-role"}, + expectError: true, + }, + { + name: "When only sts-creds provided, it should fail", + opts: AWSCredentialsOptions{STSCredentialsFile: "/tmp/creds.json"}, + expectError: true, + }, + { + name: "When sts-creds and role-arn provided, it should succeed", + opts: AWSCredentialsOptions{STSCredentialsFile: "/tmp/creds.json", RoleArn: "arn:aws:iam::123456789012:role/test-role"}, + expectError: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := tc.opts.ValidateProduct() + if tc.expectError { + if err == nil { + t.Error("Expected error but got nil") + } + } else if err != nil { + t.Errorf("Unexpected error: %v", err) + } + }) + } +} diff --git a/product-cli/cmd/cluster/aws/create.go b/product-cli/cmd/cluster/aws/create.go index 475e33044efa..59aa5318ccf7 100644 --- a/product-cli/cmd/cluster/aws/create.go +++ b/product-cli/cmd/cluster/aws/create.go @@ -23,6 +23,10 @@ func NewCreateCommand(opts *core.RawCreateOptions) *cobra.Command { hypershiftaws.BindOptions(awsOpts, cmd.Flags()) cmd.RunE = func(cmd *cobra.Command, args []string) error { + if err := hypershiftaws.ValidateProductCredentialInfo(awsOpts.Credentials, awsOpts.CredentialSecretName, opts.Namespace, opts.Kubeconfig); err != nil { + return err + } + ctx := cmd.Context() if opts.Timeout > 0 { var cancel context.CancelFunc diff --git a/product-cli/cmd/cluster/aws/destroy.go b/product-cli/cmd/cluster/aws/destroy.go index 5519046f48ed..db86dbe04546 100644 --- a/product-cli/cmd/cluster/aws/destroy.go +++ b/product-cli/cmd/cluster/aws/destroy.go @@ -30,7 +30,7 @@ func NewDestroyCommand(opts *core.DestroyOptions) *cobra.Command { opts.AWSPlatform.Credentials.BindProductFlags(cmd.Flags()) cmd.RunE = func(cmd *cobra.Command, args []string) error { - err := hypershiftaws.ValidateCredentialInfo(opts.AWSPlatform.Credentials, opts.CredentialSecretName, opts.Namespace, opts.Kubeconfig) + err := hypershiftaws.ValidateProductCredentialInfo(opts.AWSPlatform.Credentials, opts.CredentialSecretName, opts.Namespace, opts.Kubeconfig) if err != nil { return err }