diff --git a/cmd/create/network/hyperfleet.go b/cmd/create/network/hyperfleet.go index 01010eb57b..5fc3ff8e11 100644 --- a/cmd/create/network/hyperfleet.go +++ b/cmd/create/network/hyperfleet.go @@ -77,10 +77,13 @@ func runHyperfleetCreateNetwork(r *rosa.Runtime, userOptions *opts.NetworkUserOp case interactive.ModeManual: r.Reporter.Infof(helper.ManualModeHelperMessage(parsedParams, parsedTags)) r.Reporter.Infof("\nTo create the network stack manually, save the template below and use:") - r.Reporter.Infof(" aws cloudformation create-stack --stack-name %s --template-body file://template.yaml --parameters ...", parsedParams["Name"]) + r.Reporter.Infof( + " aws cloudformation create-stack --stack-name %s --template-body file://template.yaml --parameters ...", + parsedParams["Name"], + ) fmt.Println("\nCloudFormation Template:") fmt.Println("---") - fmt.Println(CloudFormationHCPTemplateFile) + fmt.Print(CloudFormationHCPTemplateFile) return default: @@ -101,6 +104,9 @@ func runHyperfleetCreateNetwork(r *rosa.Runtime, userOptions *opts.NetworkUserOp r.Reporter.Infof("Network stack created successfully") r.Reporter.Infof("Use the following to get stack outputs:") - r.Reporter.Infof(" aws cloudformation describe-stacks --stack-name %s --query 'Stacks[0].Outputs'", parsedParams["Name"]) + r.Reporter.Infof( + " aws cloudformation describe-stacks --stack-name %s --query 'Stacks[0].Outputs'", + parsedParams["Name"], + ) } } diff --git a/cmd/create/oidcconfig/hyperfleet.go b/cmd/create/oidcconfig/hyperfleet.go index ddb5bb56cc..7239659068 100644 --- a/cmd/create/oidcconfig/hyperfleet.go +++ b/cmd/create/oidcconfig/hyperfleet.go @@ -40,15 +40,6 @@ var ( } ) -// runHyperfleetCreate is a thin wrapper for direct test invocation without a real cobra.Command. -func runHyperfleetCreate(r *rosa.Runtime) { - if err := hfpathbind.RunCreateOidcConfig(context.Background(), r, nil, &hfOidcConfigInput, - &hyperfleetOidcConfigCreate{}, - ); err != nil { - hfExitFn(1) - } -} - // hyperfleetOidcConfigCreate implements hfpathbind.OidcConfigCreateHandler for rosa create oidcconfig. type hyperfleetOidcConfigCreate struct { // interactive prompting for required fields diff --git a/cmd/create/operatorroles/by_prefix_hyperfleet.go b/cmd/create/operatorroles/by_prefix_hyperfleet.go index 8f77ebcff6..8f2ade6864 100644 --- a/cmd/create/operatorroles/by_prefix_hyperfleet.go +++ b/cmd/create/operatorroles/by_prefix_hyperfleet.go @@ -189,7 +189,7 @@ func runHyperfleetCreateOperatorRoles(r *rosa.Runtime) { var trustPolicy string if spec.IsWorkerRole { // EC2 trust policy for worker role - trustPolicy = fmt.Sprintf(`{ + trustPolicy = `{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", @@ -198,7 +198,7 @@ func runHyperfleetCreateOperatorRoles(r *rosa.Runtime) { }, "Action": "sts:AssumeRole" }] -}`) +}` } else { // OIDC trust policy for operator roles trustPolicy = fmt.Sprintf(`{ @@ -216,7 +216,14 @@ func runHyperfleetCreateOperatorRoles(r *rosa.Runtime) { } } }] -}`, r.Creator.Partition, r.Creator.AccountID, oidcIssuerDomain, oidcIssuerDomain, spec.ServiceAccount, oidcIssuerDomain) +}`, + r.Creator.Partition, + r.Creator.AccountID, + oidcIssuerDomain, + oidcIssuerDomain, + spec.ServiceAccount, + oidcIssuerDomain, + ) } r.Reporter.Debugf("Creating role '%s'", roleName) @@ -303,7 +310,14 @@ func runHyperfleetCreateOperatorRoles(r *rosa.Runtime) { } } }] -}'`, r.Creator.Partition, r.Creator.AccountID, oidcIssuerDomain, oidcIssuerDomain, spec.ServiceAccount, oidcIssuerDomain) +}'`, + r.Creator.Partition, + r.Creator.AccountID, + oidcIssuerDomain, + oidcIssuerDomain, + spec.ServiceAccount, + oidcIssuerDomain, + ) } // Build tags diff --git a/cmd/list/operatorroles/hyperfleet_test.go b/cmd/list/operatorroles/hyperfleet_test.go index 5ab868bd70..f941f2a0ca 100644 --- a/cmd/list/operatorroles/hyperfleet_test.go +++ b/cmd/list/operatorroles/hyperfleet_test.go @@ -12,7 +12,6 @@ var _ = Describe("hyperfleet dispatch", func() { origHfListOperatorRoles func() enabledCalled bool - exitCode *int listCalled bool ) @@ -22,16 +21,13 @@ var _ = Describe("hyperfleet dispatch", func() { origHfListOperatorRoles = hfListOperatorRoles enabledCalled = false - exitCode = nil listCalled = false hfEnabled = func() bool { enabledCalled = true return true } - hfExitFn = func(code int) { - exitCode = &code - } + hfExitFn = func(int) {} hfListOperatorRoles = func() { listCalled = true } diff --git a/cmd/rosa/structure_test/command_args/rosa/create/cluster/command_args.yml b/cmd/rosa/structure_test/command_args/rosa/create/cluster/command_args.yml index c3902e624a..77130eebb1 100644 --- a/cmd/rosa/structure_test/command_args/rosa/create/cluster/command_args.yml +++ b/cmd/rosa/structure_test/command_args/rosa/create/cluster/command_args.yml @@ -120,6 +120,8 @@ # hyperfleet only commands # currently manually set, maybe generate as part of generate-hyperfleet? +- name: "platform" +- name: "provisioner-config-name" - name: "delete-protection" - name: "display-name" - name: "container-log-max-files" @@ -137,7 +139,6 @@ - name: "system-reserved" - name: "allowed-kernel-arguments" - name: "image-content-sources" -- name: "issuer-url" - name: "allocate-node-cidrs" - name: "advertise-address" - name: "allowed-cidr-blocks" diff --git a/cmd/rosa/structure_test/command_args/rosa/edit/cluster/command_args.yml b/cmd/rosa/structure_test/command_args/rosa/edit/cluster/command_args.yml index dd7e585381..a9c39c2b81 100644 --- a/cmd/rosa/structure_test/command_args/rosa/edit/cluster/command_args.yml +++ b/cmd/rosa/structure_test/command_args/rosa/edit/cluster/command_args.yml @@ -31,6 +31,9 @@ # hyperfleet only commands # currently manually set, maybe generate as part of generate-hyperfleet? +- name: "role-arn" +- name: "platform" +- name: "provisioner-config-name" - name: "delete-protection" - name: "display-name" - name: "container-log-max-files" @@ -45,7 +48,6 @@ - name: "serialize-image-pulls" - name: "streaming-connection-idle-timeout" - name: "image-content-sources" -- name: "issuer-url" - name: "allocate-node-cidrs" - name: "advertise-address" - name: "allowed-cidr-blocks" diff --git a/cmd/whoami/cmd_test.go b/cmd/whoami/cmd_test.go index a421d50beb..2db0d4d247 100644 --- a/cmd/whoami/cmd_test.go +++ b/cmd/whoami/cmd_test.go @@ -322,7 +322,7 @@ var _ = Describe("whoami command", func() { Expect(stdout).To(ContainSubstring("OCM Account Username:")) }) - It("Shows Platform API URL when logged in with hyperfleet only (no OCM)", func() { + It("Shows V2 API URL when logged in with hyperfleet only (no OCM)", func() { hfURL := "https://test.execute-api.us-east-1.amazonaws.com/prod" saveConfig(&config.Config{HyperfleetURL: hfURL}) @@ -331,7 +331,7 @@ var _ = Describe("whoami command", func() { return runWithRuntime(r) }, t.RosaRuntime, Cmd) Expect(err).NotTo(HaveOccurred()) - Expect(stdout).To(ContainSubstring("Platform API")) + Expect(stdout).To(ContainSubstring("V2 API")) Expect(stdout).To(ContainSubstring(hfURL)) Expect(stdout).NotTo(ContainSubstring("OCM API")) }) diff --git a/pkg/aws/client.go b/pkg/aws/client.go index b279960fca..0cdf817cb9 100644 --- a/pkg/aws/client.go +++ b/pkg/aws/client.go @@ -168,7 +168,12 @@ type Client interface { GetOpenIDConnectProviderByClusterIdTag(clusterID string) (string, error) GetOpenIDConnectProviderByOidcEndpointUrl(oidcEndpointUrl string) (string, error) GetInstanceProfilesForRole(role string) ([]string, error) - EnsureInstanceProfile(reporter reporter.Logger, instanceProfileName string, roleName string, tagList map[string]string) error + EnsureInstanceProfile( + reporter reporter.Logger, + instanceProfileName string, + roleName string, + tagList map[string]string, + ) error IsUpgradedNeededForAccountRolePolicies(rolePrefix string, version string) (bool, error) IsUpgradedNeededForAccountRolePoliciesUsingCluster(clusterID *cmv1.Cluster, version string) (bool, error) IsUpgradedNeededForOperatorRolePoliciesUsingCluster( diff --git a/pkg/aws/policies.go b/pkg/aws/policies.go index 57c2df233e..2f89597342 100644 --- a/pkg/aws/policies.go +++ b/pkg/aws/policies.go @@ -1223,7 +1223,12 @@ func (c *awsClient) GetInstanceProfilesForRole(r string) ([]string, error) { // EnsureInstanceProfile creates an instance profile if it doesn't exist and associates it with the role. // For worker roles, the instance profile must exist for EC2 instances to assume the IAM role. -func (c *awsClient) EnsureInstanceProfile(reporter reporter.Logger, instanceProfileName string, roleName string, tagList map[string]string) error { +func (c *awsClient) EnsureInstanceProfile( + reporter reporter.Logger, + instanceProfileName string, + roleName string, + tagList map[string]string, +) error { // Check if instance profile already exists _, err := c.iamClient.GetInstanceProfile(context.Background(), &iam.GetInstanceProfileInput{ InstanceProfileName: aws.String(instanceProfileName), diff --git a/pkg/hyperfleet/pathbind/cluster_create_normalize_test.go b/pkg/hyperfleet/pathbind/cluster_create_normalize_test.go new file mode 100644 index 0000000000..545357fceb --- /dev/null +++ b/pkg/hyperfleet/pathbind/cluster_create_normalize_test.go @@ -0,0 +1,155 @@ +package pathbind + +import ( + "context" + "testing" + + v1alpha1 "github.com/openshift-online/rosa-hyperfleet-api/api/v1alpha1/public" + "github.com/openshift-online/rosa-hyperfleet-api/clientset/pathbind" + "github.com/spf13/cobra" +) + +type clusterCreateBoolField struct { + flag string + set func(*ClusterCreateInput, *bool) + get func(*ClusterCreateInput) *bool +} + +var clusterCreateBoolFields = []clusterCreateBoolField{ + { + flag: "delete-protection", + set: func(i *ClusterCreateInput, p *bool) { i.DeleteProtection = p }, + get: func(i *ClusterCreateInput) *bool { return i.DeleteProtection }, + }, + { + flag: "serialize-image-pulls", + set: func(i *ClusterCreateInput, p *bool) { i.SerializeImagePulls = p }, + get: func(i *ClusterCreateInput) *bool { return i.SerializeImagePulls }, + }, + { + flag: "multi-arch", + set: func(i *ClusterCreateInput, p *bool) { i.MultiArch = p }, + get: func(i *ClusterCreateInput) *bool { return i.MultiArch }, + }, +} + +type clusterUpdateBoolField struct { + flag string + set func(*ClusterUpdateInput, *bool) + get func(*ClusterUpdateInput) *bool +} + +var clusterUpdateBoolFields = []clusterUpdateBoolField{ + { + flag: "delete-protection", + set: func(i *ClusterUpdateInput, p *bool) { i.DeleteProtection = p }, + get: func(i *ClusterUpdateInput) *bool { return i.DeleteProtection }, + }, + { + flag: "serialize-image-pulls", + set: func(i *ClusterUpdateInput, p *bool) { i.SerializeImagePulls = p }, + get: func(i *ClusterUpdateInput) *bool { return i.SerializeImagePulls }, + }, + { + flag: "multi-arch", + set: func(i *ClusterUpdateInput, p *bool) { i.MultiArch = p }, + get: func(i *ClusterUpdateInput) *bool { return i.MultiArch }, + }, +} + +func TestNormalizeClusterCreateInput_clearsUnsetBoolFlags(t *testing.T) { + for _, field := range clusterCreateBoolFields { + t.Run(field.flag, func(t *testing.T) { + cmd := &cobra.Command{} + cmd.Flags().Bool(field.flag, false, "") + + input := &ClusterCreateInput{} + field.set(input, new(bool)) + normalizeClusterCreateInput(cmd, input) + + if field.get(input) != nil { + t.Fatalf("expected nil %s, got %v", field.flag, *field.get(input)) + } + }) + } +} + +func TestNormalizeClusterCreateInput_keepsChangedBoolFlags(t *testing.T) { + for _, field := range clusterCreateBoolFields { + t.Run(field.flag, func(t *testing.T) { + cmd := &cobra.Command{} + cmd.Flags().Bool(field.flag, false, "") + if err := cmd.Flags().Set(field.flag, "true"); err != nil { + t.Fatalf("Set flag: %v", err) + } + + input := &ClusterCreateInput{} + v := true + field.set(input, &v) + normalizeClusterCreateInput(cmd, input) + + got := field.get(input) + if got == nil || !*got { + t.Fatalf("expected %s=true after explicit flag set", field.flag) + } + }) + } +} + +func TestNormalizeClusterCreateInput_clearsUnsetSerializeImagePullsFromExpand(t *testing.T) { + cmd := &cobra.Command{} + cmd.Flags().Bool("serialize-image-pulls", false, "") + + input := &ClusterCreateInput{ + SerializeImagePulls: new(bool), + } + normalizeClusterCreateInput(cmd, input) + + obj := &v1alpha1.Cluster{} + if err := pathbind.Expand(context.Background(), *input, obj); err != nil { + t.Fatalf("Expand failed: %v", err) + } + cfg := obj.Spec.HostedCluster.Configuration + if cfg != nil && cfg.Kubelet != nil && cfg.Kubelet.SerializeImagePulls != nil { + t.Fatal("expected serializeImagePulls omitted from expanded cluster spec") + } +} + +func TestNormalizeClusterUpdateInput_clearsUnsetBoolFlags(t *testing.T) { + for _, field := range clusterUpdateBoolFields { + t.Run(field.flag, func(t *testing.T) { + cmd := &cobra.Command{} + cmd.Flags().Bool(field.flag, false, "") + + input := &ClusterUpdateInput{} + field.set(input, new(bool)) + normalizeClusterUpdateInput(cmd, input) + + if field.get(input) != nil { + t.Fatalf("expected nil %s, got %v", field.flag, *field.get(input)) + } + }) + } +} + +func TestNormalizeClusterUpdateInput_keepsChangedBoolFlags(t *testing.T) { + for _, field := range clusterUpdateBoolFields { + t.Run(field.flag, func(t *testing.T) { + cmd := &cobra.Command{} + cmd.Flags().Bool(field.flag, false, "") + if err := cmd.Flags().Set(field.flag, "true"); err != nil { + t.Fatalf("Set flag: %v", err) + } + + input := &ClusterUpdateInput{} + v := true + field.set(input, &v) + normalizeClusterUpdateInput(cmd, input) + + got := field.get(input) + if got == nil || !*got { + t.Fatalf("expected %s=true after explicit flag set", field.flag) + } + }) + } +} diff --git a/pkg/rosa/runtime_test.go b/pkg/rosa/runtime_test.go index 5aa24aeb82..adbcb7d8ef 100644 --- a/pkg/rosa/runtime_test.go +++ b/pkg/rosa/runtime_test.go @@ -113,6 +113,9 @@ var _ = Describe("WithHyperFleet", func() { }) It("exits when URL has no extractable region", func() { + GinkgoT().Setenv("AWS_REGION", "") + GinkgoT().Setenv("AWS_DEFAULT_REGION", "") + hfExplicitURL = func() string { return "https://example.com/api" } r := &Runtime{Reporter: fakeRept}