From 7bb8ea0b23fbb1a4105a85acf0407d022a5b9b2e Mon Sep 17 00:00:00 2001 From: Steve Kuznetsov Date: Wed, 24 Jan 2024 13:52:07 -0700 Subject: [PATCH 1/5] golangci: importas: use correct import for the OCM API We should not be creating aliases for APIs that are something *other* than the name of the package and its version. Signed-off-by: Steve Kuznetsov --- .golangci.yml | 2 ++ .../rosacontrolplane_controller.go | 34 +++++++++---------- exp/controllers/rosamachinepool_controller.go | 8 ++--- pkg/rosa/clusters.go | 6 ++-- pkg/rosa/idps.go | 24 ++++++------- pkg/rosa/nodepools.go | 10 +++--- pkg/rosa/users.go | 8 ++--- 7 files changed, 47 insertions(+), 45 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index bd41d21015..c121ff7129 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -159,6 +159,8 @@ linters-settings: alias: apimachinerytypes - pkg: "sigs.k8s.io/cluster-api/exp/api/v1beta1" alias: expclusterv1 + - pkg: "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" + alias: clustersmgmtv1 staticcheck: go: "1.21" stylecheck: diff --git a/controlplane/rosa/controllers/rosacontrolplane_controller.go b/controlplane/rosa/controllers/rosacontrolplane_controller.go index 163d7b77ad..5cbc47ec83 100644 --- a/controlplane/rosa/controllers/rosacontrolplane_controller.go +++ b/controlplane/rosa/controllers/rosacontrolplane_controller.go @@ -26,7 +26,7 @@ import ( "strings" "time" - cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" + clustersmgmtv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -191,7 +191,7 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc if clusterID := cluster.ID(); clusterID != "" { rosaScope.ControlPlane.Status.ID = &clusterID - if cluster.Status().State() == cmv1.ClusterStateReady { + if cluster.Status().State() == clustersmgmtv1.ClusterStateReady { conditions.MarkTrue(rosaScope.ControlPlane, rosacontrolplanev1.ROSAControlPlaneReadyCondition) rosaScope.ControlPlane.Status.Ready = true @@ -220,37 +220,37 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc } // Create the cluster: - clusterBuilder := cmv1.NewCluster(). + clusterBuilder := clustersmgmtv1.NewCluster(). Name(rosaScope.RosaClusterName()). MultiAZ(true). Product( - cmv1.NewProduct(). + clustersmgmtv1.NewProduct(). ID("rosa"), ). Region( - cmv1.NewCloudRegion(). + clustersmgmtv1.NewCloudRegion(). ID(*rosaScope.ControlPlane.Spec.Region), ). FIPS(false). EtcdEncryption(false). DisableUserWorkloadMonitoring(true). Version( - cmv1.NewVersion(). + clustersmgmtv1.NewVersion(). ID(*rosaScope.ControlPlane.Spec.Version). ChannelGroup("stable"), ). ExpirationTimestamp(time.Now().Add(1 * time.Hour)). - Hypershift(cmv1.NewHypershift().Enabled(true)) + Hypershift(clustersmgmtv1.NewHypershift().Enabled(true)) - networkBuilder := cmv1.NewNetwork() + networkBuilder := clustersmgmtv1.NewNetwork() networkBuilder = networkBuilder.Type("OVNKubernetes") networkBuilder = networkBuilder.MachineCIDR(*rosaScope.ControlPlane.Spec.MachineCIDR) clusterBuilder = clusterBuilder.Network(networkBuilder) - stsBuilder := cmv1.NewSTS().RoleARN(*rosaScope.ControlPlane.Spec.InstallerRoleARN) + stsBuilder := clustersmgmtv1.NewSTS().RoleARN(*rosaScope.ControlPlane.Spec.InstallerRoleARN) // stsBuilder = stsBuilder.ExternalID(config.ExternalID) stsBuilder = stsBuilder.SupportRoleARN(*rosaScope.ControlPlane.Spec.SupportRoleARN) - roles := []*cmv1.OperatorIAMRoleBuilder{} + roles := []*clustersmgmtv1.OperatorIAMRoleBuilder{} for _, role := range []struct { Name string Namespace string @@ -298,27 +298,27 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc RoleARN: rosaScope.ControlPlane.Spec.RolesRef.NodePoolManagementARN, }, } { - roles = append(roles, cmv1.NewOperatorIAMRole(). + roles = append(roles, clustersmgmtv1.NewOperatorIAMRole(). Name(role.Name). Namespace(role.Namespace). RoleARN(role.RoleARN)) } stsBuilder = stsBuilder.OperatorIAMRoles(roles...) - instanceIAMRolesBuilder := cmv1.NewInstanceIAMRoles() + instanceIAMRolesBuilder := clustersmgmtv1.NewInstanceIAMRoles() instanceIAMRolesBuilder.WorkerRoleARN(*rosaScope.ControlPlane.Spec.WorkerRoleARN) stsBuilder = stsBuilder.InstanceIAMRoles(instanceIAMRolesBuilder) - stsBuilder.OidcConfig(cmv1.NewOidcConfig().ID(*rosaScope.ControlPlane.Spec.OIDCID)) + stsBuilder.OidcConfig(clustersmgmtv1.NewOidcConfig().ID(*rosaScope.ControlPlane.Spec.OIDCID)) stsBuilder.AutoMode(true) - awsBuilder := cmv1.NewAWS(). + awsBuilder := clustersmgmtv1.NewAWS(). AccountID(*rosaScope.ControlPlane.Spec.AccountID). BillingAccountID(*rosaScope.ControlPlane.Spec.AccountID). SubnetIDs(rosaScope.ControlPlane.Spec.Subnets...). STS(stsBuilder) clusterBuilder = clusterBuilder.AWS(awsBuilder) - clusterNodesBuilder := cmv1.NewClusterNodes() + clusterNodesBuilder := clustersmgmtv1.NewClusterNodes() clusterNodesBuilder = clusterNodesBuilder.AvailabilityZones(rosaScope.ControlPlane.Spec.AvailabilityZones...) clusterBuilder = clusterBuilder.Nodes(clusterNodesBuilder) @@ -369,7 +369,7 @@ func (r *ROSAControlPlaneReconciler) reconcileDelete(ctx context.Context, rosaSc return ctrl.Result{}, nil } -func (r *ROSAControlPlaneReconciler) reconcileKubeconfig(ctx context.Context, rosaScope *scope.ROSAControlPlaneScope, rosaClient *rosa.RosaClient, cluster *cmv1.Cluster) error { +func (r *ROSAControlPlaneReconciler) reconcileKubeconfig(ctx context.Context, rosaScope *scope.ROSAControlPlaneScope, rosaClient *rosa.RosaClient, cluster *clustersmgmtv1.Cluster) error { rosaScope.Debug("Reconciling ROSA kubeconfig for cluster", "cluster-name", rosaScope.RosaClusterName()) clusterRef := client.ObjectKeyFromObject(rosaScope.Cluster) @@ -525,7 +525,7 @@ func (r *ROSAControlPlaneReconciler) rosaClusterToROSAControlPlane(log *logger.L } } -func buildAPIEndpoint(cluster *cmv1.Cluster) (*clusterv1.APIEndpoint, error) { +func buildAPIEndpoint(cluster *clustersmgmtv1.Cluster) (*clusterv1.APIEndpoint, error) { parsedURL, err := url.ParseRequestURI(cluster.API().URL()) if err != nil { return nil, err diff --git a/exp/controllers/rosamachinepool_controller.go b/exp/controllers/rosamachinepool_controller.go index 0166ed953e..a32b47d21f 100644 --- a/exp/controllers/rosamachinepool_controller.go +++ b/exp/controllers/rosamachinepool_controller.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" + clustersmgmtv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" "github.com/pkg/errors" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime/schema" @@ -206,14 +206,14 @@ func (r *ROSAMachinePoolReconciler) reconcileNormal(ctx context.Context, return ctrl.Result{RequeueAfter: time.Second * 60}, nil } - npBuilder := cmv1.NewNodePool() + npBuilder := clustersmgmtv1.NewNodePool() npBuilder.ID(rosaMachinePool.Spec.NodePoolName). Labels(rosaMachinePool.Spec.Labels). AutoRepair(rosaMachinePool.Spec.AutoRepair) if rosaMachinePool.Spec.Autoscaling != nil { npBuilder = npBuilder.Autoscaling( - cmv1.NewNodePoolAutoscaling(). + clustersmgmtv1.NewNodePoolAutoscaling(). MinReplica(rosaMachinePool.Spec.Autoscaling.MinReplicas). MaxReplica(rosaMachinePool.Spec.Autoscaling.MaxReplicas)) } else { @@ -228,7 +228,7 @@ func (r *ROSAMachinePoolReconciler) reconcileNormal(ctx context.Context, npBuilder.Subnet(rosaMachinePool.Spec.Subnet) } - npBuilder.AWSNodePool(cmv1.NewAWSNodePool().InstanceType(rosaMachinePool.Spec.InstanceType)) + npBuilder.AWSNodePool(clustersmgmtv1.NewAWSNodePool().InstanceType(rosaMachinePool.Spec.InstanceType)) nodePoolSpec, err := npBuilder.Build() if err != nil { diff --git a/pkg/rosa/clusters.go b/pkg/rosa/clusters.go index 1b6605245a..cd5c25d951 100644 --- a/pkg/rosa/clusters.go +++ b/pkg/rosa/clusters.go @@ -3,7 +3,7 @@ package rosa import ( "fmt" - cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" + clustersmgmtv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" ) const ( @@ -11,7 +11,7 @@ const ( ) // CreateCluster creates a new ROSA cluster using the specified spec. -func (c *RosaClient) CreateCluster(spec *cmv1.Cluster) (*cmv1.Cluster, error) { +func (c *RosaClient) CreateCluster(spec *clustersmgmtv1.Cluster) (*clustersmgmtv1.Cluster, error) { cluster, err := c.ocm.ClustersMgmt().V1().Clusters(). Add(). Body(spec). @@ -39,7 +39,7 @@ func (c *RosaClient) DeleteCluster(clusterID string) error { } // GetCluster retrieves the ROSA/OCM cluster object. -func (c *RosaClient) GetCluster() (*cmv1.Cluster, error) { +func (c *RosaClient) GetCluster() (*clustersmgmtv1.Cluster, error) { clusterKey := c.rosaScope.RosaClusterName() query := fmt.Sprintf("%s AND (id = '%s' OR name = '%s' OR external_id = '%s')", getClusterFilter(c.rosaScope.ControlPlane.Spec.CreatorARN), diff --git a/pkg/rosa/idps.go b/pkg/rosa/idps.go index 8bd6d01f39..d2f03bf40d 100644 --- a/pkg/rosa/idps.go +++ b/pkg/rosa/idps.go @@ -4,11 +4,11 @@ import ( "fmt" "net/http" - cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" + clustersmgmtv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" ) // ListIdentityProviders retrieves the list of identity providers. -func (c *RosaClient) ListIdentityProviders(clusterID string) ([]*cmv1.IdentityProvider, error) { +func (c *RosaClient) ListIdentityProviders(clusterID string) ([]*clustersmgmtv1.IdentityProvider, error) { response, err := c.ocm.ClustersMgmt().V1(). Clusters().Cluster(clusterID). IdentityProviders(). @@ -22,7 +22,7 @@ func (c *RosaClient) ListIdentityProviders(clusterID string) ([]*cmv1.IdentityPr } // CreateIdentityProvider adds a new identity provider to the cluster. -func (c *RosaClient) CreateIdentityProvider(clusterID string, idp *cmv1.IdentityProvider) (*cmv1.IdentityProvider, error) { +func (c *RosaClient) CreateIdentityProvider(clusterID string, idp *clustersmgmtv1.IdentityProvider) (*clustersmgmtv1.IdentityProvider, error) { response, err := c.ocm.ClustersMgmt().V1(). Clusters().Cluster(clusterID). IdentityProviders(). @@ -35,7 +35,7 @@ func (c *RosaClient) CreateIdentityProvider(clusterID string, idp *cmv1.Identity } // GetHTPasswdUserList retrieves the list of users of the provided _HTPasswd_ identity provider. -func (c *RosaClient) GetHTPasswdUserList(clusterID, htpasswdIDPId string) (*cmv1.HTPasswdUserList, error) { +func (c *RosaClient) GetHTPasswdUserList(clusterID, htpasswdIDPId string) (*clustersmgmtv1.HTPasswdUserList, error) { listResponse, err := c.ocm.ClustersMgmt().V1().Clusters().Cluster(clusterID). IdentityProviders().IdentityProvider(htpasswdIDPId).HtpasswdUsers().List().Send() if err != nil { @@ -50,7 +50,7 @@ func (c *RosaClient) GetHTPasswdUserList(clusterID, htpasswdIDPId string) (*cmv1 // AddHTPasswdUser adds a new user to the provided _HTPasswd_ identity provider. func (c *RosaClient) AddHTPasswdUser(username, password, clusterID, idpID string) error { - htpasswdUser, _ := cmv1.NewHTPasswdUser().Username(username).Password(password).Build() + htpasswdUser, _ := clustersmgmtv1.NewHTPasswdUser().Username(username).Password(password).Build() response, err := c.ocm.ClustersMgmt().V1().Clusters().Cluster(clusterID). IdentityProviders().IdentityProvider(idpID).HtpasswdUsers().Add().Body(htpasswdUser).Send() if err != nil { @@ -97,11 +97,11 @@ func (c *RosaClient) CreateAdminUserIfNotExist(clusterID, username, password str } // No ClusterAdmin IDP exists, create an Htpasswd IDP - htpasswdIDP := cmv1.NewHTPasswdIdentityProvider().Users(cmv1.NewHTPasswdUserList().Items( - cmv1.NewHTPasswdUser().Username(username).Password(password), + htpasswdIDP := clustersmgmtv1.NewHTPasswdIdentityProvider().Users(clustersmgmtv1.NewHTPasswdUserList().Items( + clustersmgmtv1.NewHTPasswdUser().Username(username).Password(password), )) - clusterAdminIDP, err := cmv1.NewIdentityProvider(). - Type(cmv1.IdentityProviderTypeHtpasswd). + clusterAdminIDP, err := clustersmgmtv1.NewIdentityProvider(). + Type(clustersmgmtv1.IdentityProviderTypeHtpasswd). Name(clusterAdminIDPname). Htpasswd(htpasswdIDP). Build() @@ -128,7 +128,7 @@ func (c *RosaClient) CreateAdminUserIfNotExist(clusterID, username, password str } func (c *RosaClient) findExistingClusterAdminIDP(clusterID string) ( - htpasswdIDP *cmv1.IdentityProvider, userList *cmv1.HTPasswdUserList, reterr error) { + htpasswdIDP *clustersmgmtv1.IdentityProvider, userList *clustersmgmtv1.HTPasswdUserList, reterr error) { idps, err := c.ListIdentityProviders(clusterID) if err != nil { reterr = fmt.Errorf("failed to get identity providers for cluster '%s': %v", clusterID, err) @@ -152,9 +152,9 @@ func (c *RosaClient) findExistingClusterAdminIDP(clusterID string) ( return } -func hasUser(username string, userList *cmv1.HTPasswdUserList) bool { +func hasUser(username string, userList *clustersmgmtv1.HTPasswdUserList) bool { hasUser := false - userList.Each(func(user *cmv1.HTPasswdUser) bool { + userList.Each(func(user *clustersmgmtv1.HTPasswdUser) bool { if user.Username() == username { hasUser = true return false diff --git a/pkg/rosa/nodepools.go b/pkg/rosa/nodepools.go index 575b54bfd1..260922f866 100644 --- a/pkg/rosa/nodepools.go +++ b/pkg/rosa/nodepools.go @@ -1,9 +1,9 @@ package rosa -import cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" +import clustersmgmtv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" // CreateNodePool adds a new node pool to the cluster. -func (c *RosaClient) CreateNodePool(clusterID string, nodePool *cmv1.NodePool) (*cmv1.NodePool, error) { +func (c *RosaClient) CreateNodePool(clusterID string, nodePool *clustersmgmtv1.NodePool) (*clustersmgmtv1.NodePool, error) { response, err := c.ocm.ClustersMgmt().V1(). Clusters().Cluster(clusterID). NodePools(). @@ -16,7 +16,7 @@ func (c *RosaClient) CreateNodePool(clusterID string, nodePool *cmv1.NodePool) ( } // GetNodePools retrieves the list of node pools in the cluster. -func (c *RosaClient) GetNodePools(clusterID string) ([]*cmv1.NodePool, error) { +func (c *RosaClient) GetNodePools(clusterID string) ([]*clustersmgmtv1.NodePool, error) { response, err := c.ocm.ClustersMgmt().V1(). Clusters().Cluster(clusterID). NodePools(). @@ -29,7 +29,7 @@ func (c *RosaClient) GetNodePools(clusterID string) ([]*cmv1.NodePool, error) { } // GetNodePool retrieves the details of the specified node pool. -func (c *RosaClient) GetNodePool(clusterID string, nodePoolID string) (*cmv1.NodePool, bool, error) { +func (c *RosaClient) GetNodePool(clusterID string, nodePoolID string) (*clustersmgmtv1.NodePool, bool, error) { response, err := c.ocm.ClustersMgmt().V1(). Clusters().Cluster(clusterID). NodePools(). @@ -46,7 +46,7 @@ func (c *RosaClient) GetNodePool(clusterID string, nodePoolID string) (*cmv1.Nod } // UpdateNodePool updates the specified node pool. -func (c *RosaClient) UpdateNodePool(clusterID string, nodePool *cmv1.NodePool) (*cmv1.NodePool, error) { +func (c *RosaClient) UpdateNodePool(clusterID string, nodePool *clustersmgmtv1.NodePool) (*clustersmgmtv1.NodePool, error) { response, err := c.ocm.ClustersMgmt().V1(). Clusters().Cluster(clusterID). NodePools().NodePool(nodePool.ID()). diff --git a/pkg/rosa/users.go b/pkg/rosa/users.go index 38203536f2..5055839d4d 100644 --- a/pkg/rosa/users.go +++ b/pkg/rosa/users.go @@ -4,11 +4,11 @@ import ( "fmt" "net/http" - cmv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" + clustersmgmtv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" ) // CreateUserIfNotExist creates a new user with `username` and adds it to the group if it doesn't already exist. -func (c *RosaClient) CreateUserIfNotExist(clusterID string, group, username string) (*cmv1.User, error) { +func (c *RosaClient) CreateUserIfNotExist(clusterID string, group, username string) (*clustersmgmtv1.User, error) { response, err := c.ocm.ClustersMgmt().V1(). Clusters().Cluster(clusterID). Groups().Group(group). @@ -21,7 +21,7 @@ func (c *RosaClient) CreateUserIfNotExist(clusterID string, group, username stri return nil, handleErr(response.Error(), err) } - user, err := cmv1.NewUser().ID(username).Build() + user, err := clustersmgmtv1.NewUser().ID(username).Build() if err != nil { return nil, fmt.Errorf("failed to create user '%s' for cluster '%s'", username, clusterID) } @@ -30,7 +30,7 @@ func (c *RosaClient) CreateUserIfNotExist(clusterID string, group, username stri } // CreateUser adds a new user to the group. -func (c *RosaClient) CreateUser(clusterID string, group string, user *cmv1.User) (*cmv1.User, error) { +func (c *RosaClient) CreateUser(clusterID string, group string, user *clustersmgmtv1.User) (*clustersmgmtv1.User, error) { response, err := c.ocm.ClustersMgmt().V1(). Clusters().Cluster(clusterID). Groups().Group(group). From 2c62cf922bea34bb39540275ec240392eccd0d70 Mon Sep 17 00:00:00 2001 From: Steve Kuznetsov Date: Wed, 24 Jan 2024 13:53:41 -0700 Subject: [PATCH 2/5] controlplane/rosa: isolate CAPI -> OCM object transform Signed-off-by: Steve Kuznetsov --- .../rosacontrolplane_controller.go | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/controlplane/rosa/controllers/rosacontrolplane_controller.go b/controlplane/rosa/controllers/rosacontrolplane_controller.go index 5cbc47ec83..3fd340ec27 100644 --- a/controlplane/rosa/controllers/rosacontrolplane_controller.go +++ b/controlplane/rosa/controllers/rosacontrolplane_controller.go @@ -219,6 +219,28 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc return ctrl.Result{RequeueAfter: time.Second * 60}, nil } + clusterSpec, err := ocmCluster(rosaScope, nil) + if err != nil { + return ctrl.Result{}, err + } + + newCluster, err := rosaClient.CreateCluster(clusterSpec) + if err != nil { + rosaScope.Info("error", "error", err) + return ctrl.Result{RequeueAfter: 10 * time.Second}, nil + } + + rosaScope.Info("cluster created", "state", newCluster.Status().State()) + clusterID := newCluster.ID() + rosaScope.ControlPlane.Status.ID = &clusterID + + return ctrl.Result{}, nil +} + +func ocmCluster(rosaScope *scope.ROSAControlPlaneScope, now func() time.Time) (*clustersmgmtv1.Cluster, error) { + if now == nil { + now = time.Now + } // Create the cluster: clusterBuilder := clustersmgmtv1.NewCluster(). Name(rosaScope.RosaClusterName()). @@ -239,7 +261,7 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc ID(*rosaScope.ControlPlane.Spec.Version). ChannelGroup("stable"), ). - ExpirationTimestamp(time.Now().Add(1 * time.Hour)). + ExpirationTimestamp(now().Add(1 * time.Hour)). Hypershift(clustersmgmtv1.NewHypershift().Enabled(true)) networkBuilder := clustersmgmtv1.NewNetwork() @@ -326,22 +348,7 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc clusterProperties[rosaCreatorArnProperty] = *rosaScope.ControlPlane.Spec.CreatorARN clusterBuilder = clusterBuilder.Properties(clusterProperties) - clusterSpec, err := clusterBuilder.Build() - if err != nil { - return ctrl.Result{}, fmt.Errorf("failed to create description of cluster: %v", err) - } - - newCluster, err := rosaClient.CreateCluster(clusterSpec) - if err != nil { - rosaScope.Info("error", "error", err) - return ctrl.Result{RequeueAfter: 10 * time.Second}, nil - } - - rosaScope.Info("cluster created", "state", newCluster.Status().State()) - clusterID := newCluster.ID() - rosaScope.ControlPlane.Status.ID = &clusterID - - return ctrl.Result{}, nil + return clusterBuilder.Build() } func (r *ROSAControlPlaneReconciler) reconcileDelete(ctx context.Context, rosaScope *scope.ROSAControlPlaneScope) (res ctrl.Result, reterr error) { From 46eda01bce323f990a9e420bb0b012f745cd981e Mon Sep 17 00:00:00 2001 From: Steve Kuznetsov Date: Wed, 24 Jan 2024 14:00:23 -0700 Subject: [PATCH 3/5] controlplane/rosa: add a test for the CAPI -> OCM transform Signed-off-by: Steve Kuznetsov --- .../rosacontrolplane_controller_test.go | 77 ++++++++++++ .../testdata/zz_fixture_TestOCMCluster.json | 99 +++++++++++++++ test/helpers/fixture/fixture.go | 113 ++++++++++++++++++ 3 files changed, 289 insertions(+) create mode 100644 controlplane/rosa/controllers/rosacontrolplane_controller_test.go create mode 100644 controlplane/rosa/controllers/testdata/zz_fixture_TestOCMCluster.json create mode 100644 test/helpers/fixture/fixture.go diff --git a/controlplane/rosa/controllers/rosacontrolplane_controller_test.go b/controlplane/rosa/controllers/rosacontrolplane_controller_test.go new file mode 100644 index 0000000000..e2321efa87 --- /dev/null +++ b/controlplane/rosa/controllers/rosacontrolplane_controller_test.go @@ -0,0 +1,77 @@ +package controllers + +import ( + "bytes" + "testing" + "time" + + clustersmgmtv1 "github.com/openshift-online/ocm-sdk-go/clustersmgmt/v1" + corev1 "k8s.io/api/core/v1" + clocktesting "k8s.io/utils/clock/testing" + "k8s.io/utils/ptr" + + rosacontrolplanev1beta2 "sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/rosa/api/v1beta2" + "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/scope" + testutil "sigs.k8s.io/cluster-api-provider-aws/v2/test/helpers/fixture" + clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" +) + +func TestOCMCluster(t *testing.T) { + now, err := time.Parse(time.RFC3339Nano, "2006-01-02T15:04:05.999999999Z") + if err != nil { + t.Fatalf("could not parse time: %v", err) + } + clock := clocktesting.NewFakeClock(now) + + for _, testCase := range []struct { + name string + in *rosacontrolplanev1beta2.ROSAControlPlane + }{ + { + name: "exhaustive case", + in: &rosacontrolplanev1beta2.ROSAControlPlane{ + Spec: rosacontrolplanev1beta2.RosaControlPlaneSpec{ + RosaClusterName: "rosa-cluster-name", + Subnets: []string{"subnet-1", "subnet-2"}, + AvailabilityZones: []string{"az-1", "az-2"}, + MachineCIDR: ptr.To("machine-cidr"), + Region: ptr.To("region"), + Version: ptr.To("version"), + ControlPlaneEndpoint: clusterv1.APIEndpoint{ + Host: "example.com", + Port: 1234, + }, + RolesRef: rosacontrolplanev1beta2.AWSRolesRef{ + IngressARN: "ingress-arn", + ImageRegistryARN: "image-registry-arn", + StorageARN: "storage-arn", + NetworkARN: "network-arn", + KubeCloudControllerARN: "kube-cloud-controller-arn", + NodePoolManagementARN: "node-pool-management-arn", + ControlPlaneOperatorARN: "control-plane-operator-arn", + KMSProviderARN: "kms-provider-arn", + }, + OIDCID: ptr.To("oidc-id"), + AccountID: ptr.To("account-id"), + CreatorARN: ptr.To("creator-arn"), + InstallerRoleARN: ptr.To("installer-role-arn"), + SupportRoleARN: ptr.To("support-role-arn"), + WorkerRoleARN: ptr.To("worker-role-arn"), + CredentialsSecretRef: &corev1.LocalObjectReference{ + Name: "credentials-secret-name", + }, + }, + }, + }, + } { + got, err := ocmCluster(&scope.ROSAControlPlaneScope{ControlPlane: testCase.in}, clock.Now) + if err != nil { + t.Fatalf("failed to create cluster: %v", err) + } + out := bytes.Buffer{} + if err := clustersmgmtv1.MarshalCluster(got, &out); err != nil { + t.Fatalf("failed to marshal cluster: %v", err) + } + testutil.CompareWithFixture(t, out.Bytes(), testutil.WithExtension(".json")) + } +} diff --git a/controlplane/rosa/controllers/testdata/zz_fixture_TestOCMCluster.json b/controlplane/rosa/controllers/testdata/zz_fixture_TestOCMCluster.json new file mode 100644 index 0000000000..3e3f4c3a70 --- /dev/null +++ b/controlplane/rosa/controllers/testdata/zz_fixture_TestOCMCluster.json @@ -0,0 +1,99 @@ +{ + "kind": "Cluster", + "aws": { + "sts": { + "auto_mode": true, + "instance_iam_roles": { + "worker_role_arn": "worker-role-arn" + }, + "oidc_config": { + "id": "oidc-id" + }, + "operator_iam_roles": [ + { + "name": "cloud-credentials", + "namespace": "openshift-ingress-operator", + "role_arn": "ingress-arn" + }, + { + "name": "installer-cloud-credentials", + "namespace": "openshift-image-registry", + "role_arn": "image-registry-arn" + }, + { + "name": "ebs-cloud-credentials", + "namespace": "openshift-cluster-csi-drivers", + "role_arn": "storage-arn" + }, + { + "name": "cloud-credentials", + "namespace": "openshift-cloud-network-config-controller", + "role_arn": "network-arn" + }, + { + "name": "kube-controller-manager", + "namespace": "kube-system", + "role_arn": "kube-cloud-controller-arn" + }, + { + "name": "kms-provider", + "namespace": "kube-system", + "role_arn": "kms-provider-arn" + }, + { + "name": "control-plane-operator", + "namespace": "kube-system", + "role_arn": "control-plane-operator-arn" + }, + { + "name": "capa-controller-manager", + "namespace": "kube-system", + "role_arn": "node-pool-management-arn" + } + ], + "role_arn": "installer-role-arn", + "support_role_arn": "support-role-arn" + }, + "account_id": "account-id", + "billing_account_id": "account-id", + "subnet_ids": [ + "subnet-1", + "subnet-2" + ] + }, + "fips": false, + "disable_user_workload_monitoring": true, + "etcd_encryption": false, + "expiration_timestamp": "2006-01-02T16:04:05Z", + "hypershift": { + "enabled": true + }, + "multi_az": true, + "name": "rosa-cluster-name", + "network": { + "machine_cidr": "machine-cidr", + "type": "OVNKubernetes" + }, + "nodes": { + "availability_zones": [ + "az-1", + "az-2" + ] + }, + "product": { + "kind": "Product", + "id": "rosa" + }, + "properties": { + "rosa_creator_arn": "creator-arn" + }, + "region": { + "kind": "CloudRegion", + "id": "region" + }, + "version": { + "kind": "Version", + "id": "version", + "channel_group": "stable" + } +} \ No newline at end of file diff --git a/test/helpers/fixture/fixture.go b/test/helpers/fixture/fixture.go new file mode 100644 index 0000000000..762b15f605 --- /dev/null +++ b/test/helpers/fixture/fixture.go @@ -0,0 +1,113 @@ +package testutil + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "sigs.k8s.io/yaml" +) + +// CompareWithFixture will compare output with a test fixture and allows to automatically update them +// by setting the UPDATE env var. +// If output is not a []byte or string, it will get serialized as yaml prior to the comparison. +// The fixtures are stored in $PWD/testdata/prefix${testName}.yaml. +func CompareWithFixture(t *testing.T, output interface{}, opts ...option) { + t.Helper() + options := &options{ + Extension: ".yaml", + } + for _, opt := range opts { + opt(options) + } + + var serializedOutput []byte + switch v := output.(type) { + case []byte: + serializedOutput = v + case string: + serializedOutput = []byte(v) + default: + serialized, err := yaml.Marshal(v) + if err != nil { + t.Fatalf("failed to yaml marshal output of type %T: %v", output, err) + } + serializedOutput = serialized + } + + golden, err := golden(t, options) + if err != nil { + t.Fatalf("failed to get absolute path to testdata file: %v", err) + } + if os.Getenv("UPDATE") != "" { + if err := os.MkdirAll(filepath.Dir(filepath.Clean(golden)), 0755); err != nil { + t.Fatalf("failed to create fixture directory: %v", err) + } + if err := os.WriteFile(filepath.Clean(golden), serializedOutput, 0600); err != nil { + t.Fatalf("failed to write updated fixture: %v", err) + } + } + expected, err := os.ReadFile(filepath.Clean(golden)) + if err != nil { + t.Fatalf("failed to read testdata file: %v", err) + } + + if diff := cmp.Diff(string(expected), string(serializedOutput)); diff != "" { + t.Errorf("got diff between expected and actual result:\nfile: %s\ndiff:\n%s\n\nIf this is expected, re-run the test with `UPDATE=true go test ./...` to update the fixtures.", golden, diff) + } +} + +// WithPrefix adds an optional prefix to the fixture file. +func WithPrefix(prefix string) option { + return func(o *options) { + o.Prefix = prefix + } +} + +// WithSuffix adds an optional suffix to the fixture file. +func WithSuffix(suffix string) option { + return func(o *options) { + o.Suffix = suffix + } +} + +// WithExtension changes the extension of fixture file. +func WithExtension(extension string) option { + return func(o *options) { + o.Extension = extension + } +} + +type options struct { + Prefix string + Suffix string + Extension string +} + +type option func(*options) + +// golden determines the golden file to use. +func golden(t *testing.T, opts *options) (string, error) { + t.Helper() + if opts.Extension == "" { + opts.Extension = ".yaml" + } + return filepath.Abs(filepath.Join("testdata", sanitizeFilename(opts.Prefix+t.Name()+opts.Suffix)) + opts.Extension) +} + +func sanitizeFilename(s string) string { + result := strings.Builder{} + for _, r := range s { + if (r >= 'a' && r < 'z') || (r >= 'A' && r < 'Z') || r == '_' || r == '.' || (r >= '0' && r <= '9') { + // The thing is documented as returning a nil error so lets just drop it + _, _ = result.WriteRune(r) + continue + } + if !strings.HasSuffix(result.String(), "_") { + result.WriteRune('_') + } + } + return "zz_fixture_" + result.String() +} From e278d2e97f8c515118331e1cdcc5c08764a897d9 Mon Sep 17 00:00:00 2001 From: Steve Kuznetsov Date: Wed, 24 Jan 2024 10:32:21 -0700 Subject: [PATCH 4/5] controlpane/rosa: make cluster declaration idomatic Signed-off-by: Steve Kuznetsov --- .../rosacontrolplane_controller.go | 167 ++++++++---------- .../rosacontrolplane_controller_test.go | 4 +- 2 files changed, 74 insertions(+), 97 deletions(-) diff --git a/controlplane/rosa/controllers/rosacontrolplane_controller.go b/controlplane/rosa/controllers/rosacontrolplane_controller.go index 3fd340ec27..4bc54d5e7c 100644 --- a/controlplane/rosa/controllers/rosacontrolplane_controller.go +++ b/controlplane/rosa/controllers/rosacontrolplane_controller.go @@ -219,7 +219,7 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc return ctrl.Result{RequeueAfter: time.Second * 60}, nil } - clusterSpec, err := ocmCluster(rosaScope, nil) + clusterSpec, err := ocmCluster(rosaScope.ControlPlane, nil) if err != nil { return ctrl.Result{}, err } @@ -237,13 +237,12 @@ func (r *ROSAControlPlaneReconciler) reconcileNormal(ctx context.Context, rosaSc return ctrl.Result{}, nil } -func ocmCluster(rosaScope *scope.ROSAControlPlaneScope, now func() time.Time) (*clustersmgmtv1.Cluster, error) { +func ocmCluster(controlPlane *rosacontrolplanev1.ROSAControlPlane, now func() time.Time) (*clustersmgmtv1.Cluster, error) { if now == nil { now = time.Now } - // Create the cluster: - clusterBuilder := clustersmgmtv1.NewCluster(). - Name(rosaScope.RosaClusterName()). + return clustersmgmtv1.NewCluster(). + Name(controlPlane.Spec.RosaClusterName). MultiAZ(true). Product( clustersmgmtv1.NewProduct(). @@ -251,104 +250,84 @@ func ocmCluster(rosaScope *scope.ROSAControlPlaneScope, now func() time.Time) (* ). Region( clustersmgmtv1.NewCloudRegion(). - ID(*rosaScope.ControlPlane.Spec.Region), + ID(*controlPlane.Spec.Region), ). FIPS(false). EtcdEncryption(false). DisableUserWorkloadMonitoring(true). Version( clustersmgmtv1.NewVersion(). - ID(*rosaScope.ControlPlane.Spec.Version). + ID(*controlPlane.Spec.Version). ChannelGroup("stable"), ). ExpirationTimestamp(now().Add(1 * time.Hour)). - Hypershift(clustersmgmtv1.NewHypershift().Enabled(true)) - - networkBuilder := clustersmgmtv1.NewNetwork() - networkBuilder = networkBuilder.Type("OVNKubernetes") - networkBuilder = networkBuilder.MachineCIDR(*rosaScope.ControlPlane.Spec.MachineCIDR) - clusterBuilder = clusterBuilder.Network(networkBuilder) - - stsBuilder := clustersmgmtv1.NewSTS().RoleARN(*rosaScope.ControlPlane.Spec.InstallerRoleARN) - // stsBuilder = stsBuilder.ExternalID(config.ExternalID) - stsBuilder = stsBuilder.SupportRoleARN(*rosaScope.ControlPlane.Spec.SupportRoleARN) - roles := []*clustersmgmtv1.OperatorIAMRoleBuilder{} - for _, role := range []struct { - Name string - Namespace string - RoleARN string - Path string - }{ - { - Name: "cloud-credentials", - Namespace: "openshift-ingress-operator", - RoleARN: rosaScope.ControlPlane.Spec.RolesRef.IngressARN, - }, - { - Name: "installer-cloud-credentials", - Namespace: "openshift-image-registry", - RoleARN: rosaScope.ControlPlane.Spec.RolesRef.ImageRegistryARN, - }, - { - Name: "ebs-cloud-credentials", - Namespace: "openshift-cluster-csi-drivers", - RoleARN: rosaScope.ControlPlane.Spec.RolesRef.StorageARN, - }, - { - Name: "cloud-credentials", - Namespace: "openshift-cloud-network-config-controller", - RoleARN: rosaScope.ControlPlane.Spec.RolesRef.NetworkARN, - }, - { - Name: "kube-controller-manager", - Namespace: "kube-system", - RoleARN: rosaScope.ControlPlane.Spec.RolesRef.KubeCloudControllerARN, - }, - { - Name: "kms-provider", - Namespace: "kube-system", - RoleARN: rosaScope.ControlPlane.Spec.RolesRef.KMSProviderARN, - }, - { - Name: "control-plane-operator", - Namespace: "kube-system", - RoleARN: rosaScope.ControlPlane.Spec.RolesRef.ControlPlaneOperatorARN, - }, - { - Name: "capa-controller-manager", - Namespace: "kube-system", - RoleARN: rosaScope.ControlPlane.Spec.RolesRef.NodePoolManagementARN, - }, - } { - roles = append(roles, clustersmgmtv1.NewOperatorIAMRole(). - Name(role.Name). - Namespace(role.Namespace). - RoleARN(role.RoleARN)) - } - stsBuilder = stsBuilder.OperatorIAMRoles(roles...) - - instanceIAMRolesBuilder := clustersmgmtv1.NewInstanceIAMRoles() - instanceIAMRolesBuilder.WorkerRoleARN(*rosaScope.ControlPlane.Spec.WorkerRoleARN) - stsBuilder = stsBuilder.InstanceIAMRoles(instanceIAMRolesBuilder) - stsBuilder.OidcConfig(clustersmgmtv1.NewOidcConfig().ID(*rosaScope.ControlPlane.Spec.OIDCID)) - stsBuilder.AutoMode(true) - - awsBuilder := clustersmgmtv1.NewAWS(). - AccountID(*rosaScope.ControlPlane.Spec.AccountID). - BillingAccountID(*rosaScope.ControlPlane.Spec.AccountID). - SubnetIDs(rosaScope.ControlPlane.Spec.Subnets...). - STS(stsBuilder) - clusterBuilder = clusterBuilder.AWS(awsBuilder) - - clusterNodesBuilder := clustersmgmtv1.NewClusterNodes() - clusterNodesBuilder = clusterNodesBuilder.AvailabilityZones(rosaScope.ControlPlane.Spec.AvailabilityZones...) - clusterBuilder = clusterBuilder.Nodes(clusterNodesBuilder) - - clusterProperties := map[string]string{} - clusterProperties[rosaCreatorArnProperty] = *rosaScope.ControlPlane.Spec.CreatorARN - - clusterBuilder = clusterBuilder.Properties(clusterProperties) - return clusterBuilder.Build() + Hypershift(clustersmgmtv1.NewHypershift().Enabled(true)). + Network( + clustersmgmtv1.NewNetwork(). + Type("OVNKubernetes"). + MachineCIDR(*controlPlane.Spec.MachineCIDR), + ). + AWS( + clustersmgmtv1.NewAWS(). + AccountID(*controlPlane.Spec.AccountID). + BillingAccountID(*controlPlane.Spec.AccountID). + SubnetIDs(controlPlane.Spec.Subnets...). + STS( + clustersmgmtv1.NewSTS(). + RoleARN(*controlPlane.Spec.InstallerRoleARN). + SupportRoleARN(*controlPlane.Spec.SupportRoleARN). + OperatorIAMRoles( + clustersmgmtv1.NewOperatorIAMRole(). + Name("cloud-credentials"). + Namespace("openshift-ingress-operator"). + RoleARN(controlPlane.Spec.RolesRef.IngressARN), + clustersmgmtv1.NewOperatorIAMRole(). + Name("installer-cloud-credentials"). + Namespace("openshift-image-registry"). + RoleARN(controlPlane.Spec.RolesRef.ImageRegistryARN), + clustersmgmtv1.NewOperatorIAMRole(). + Name("ebs-cloud-credentials"). + Namespace("openshift-cluster-csi-drivers"). + RoleARN(controlPlane.Spec.RolesRef.StorageARN), + clustersmgmtv1.NewOperatorIAMRole(). + Name("cloud-credentials"). + Namespace("openshift-cloud-network-config-controller"). + RoleARN(controlPlane.Spec.RolesRef.NetworkARN), + clustersmgmtv1.NewOperatorIAMRole(). + Name("kube-controller-manager"). + Namespace("kube-system"). + RoleARN(controlPlane.Spec.RolesRef.KubeCloudControllerARN), + clustersmgmtv1.NewOperatorIAMRole(). + Name("kms-provider"). + Namespace("kube-system"). + RoleARN(controlPlane.Spec.RolesRef.KMSProviderARN), + clustersmgmtv1.NewOperatorIAMRole(). + Name("control-plane-operator"). + Namespace("kube-system"). + RoleARN(controlPlane.Spec.RolesRef.ControlPlaneOperatorARN), + clustersmgmtv1.NewOperatorIAMRole(). + Name("capa-controller-manager"). + Namespace("kube-system"). + RoleARN(controlPlane.Spec.RolesRef.NodePoolManagementARN), + ). + InstanceIAMRoles( + clustersmgmtv1.NewInstanceIAMRoles(). + WorkerRoleARN(*controlPlane.Spec.WorkerRoleARN), + ). + OidcConfig( + clustersmgmtv1.NewOidcConfig().ID(*controlPlane.Spec.OIDCID), + ). + AutoMode(true), + ), + ). + Nodes( + clustersmgmtv1.NewClusterNodes(). + AvailabilityZones(controlPlane.Spec.AvailabilityZones...), + ). + Properties(map[string]string{ + rosaCreatorArnProperty: *controlPlane.Spec.CreatorARN, + }). + Build() } func (r *ROSAControlPlaneReconciler) reconcileDelete(ctx context.Context, rosaScope *scope.ROSAControlPlaneScope) (res ctrl.Result, reterr error) { diff --git a/controlplane/rosa/controllers/rosacontrolplane_controller_test.go b/controlplane/rosa/controllers/rosacontrolplane_controller_test.go index e2321efa87..95e53c5aaa 100644 --- a/controlplane/rosa/controllers/rosacontrolplane_controller_test.go +++ b/controlplane/rosa/controllers/rosacontrolplane_controller_test.go @@ -11,7 +11,6 @@ import ( "k8s.io/utils/ptr" rosacontrolplanev1beta2 "sigs.k8s.io/cluster-api-provider-aws/v2/controlplane/rosa/api/v1beta2" - "sigs.k8s.io/cluster-api-provider-aws/v2/pkg/cloud/scope" testutil "sigs.k8s.io/cluster-api-provider-aws/v2/test/helpers/fixture" clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" ) @@ -22,7 +21,6 @@ func TestOCMCluster(t *testing.T) { t.Fatalf("could not parse time: %v", err) } clock := clocktesting.NewFakeClock(now) - for _, testCase := range []struct { name string in *rosacontrolplanev1beta2.ROSAControlPlane @@ -64,7 +62,7 @@ func TestOCMCluster(t *testing.T) { }, }, } { - got, err := ocmCluster(&scope.ROSAControlPlaneScope{ControlPlane: testCase.in}, clock.Now) + got, err := ocmCluster(testCase.in, clock.Now) if err != nil { t.Fatalf("failed to create cluster: %v", err) } From 3fbec965e9d69166f89eb52366048440f596e231 Mon Sep 17 00:00:00 2001 From: Steve Kuznetsov Date: Wed, 24 Jan 2024 14:11:52 -0700 Subject: [PATCH 5/5] controlplane/rosa: support Private Link Signed-off-by: Steve Kuznetsov --- Makefile | 2 +- ...ne.cluster.x-k8s.io_rosacontrolplanes.yaml | 23 +++++++++++ .../api/v1beta2/rosacontrolplane_types.go | 16 ++++++++ .../rosa/api/v1beta2/zz_generated.deepcopy.go | 41 +++++++++++++++++++ .../rosacontrolplane_controller.go | 15 +++++++ .../rosacontrolplane_controller_test.go | 6 +++ .../testdata/zz_fixture_TestOCMCluster.json | 13 ++++++ 7 files changed, 115 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b43f76940c..06eae3f99c 100644 --- a/Makefile +++ b/Makefile @@ -76,7 +76,7 @@ DOCKER_BUILDKIT=1 export ACK_GINKGO_DEPRECATIONS := 1.16.4 # Set --output-base for conversion-gen if we are not within GOPATH -ifneq ($(abspath $(REPO_ROOT)),$(shell go env GOPATH)/src/sigs.k8s.io/cluster-api-provider-aws) +ifneq ($(abspath $(REPO_ROOT)),$(abspath $(shell go env GOPATH)/src/sigs.k8s.io/cluster-api-provider-aws)) GEN_OUTPUT_BASE := --output-base=$(REPO_ROOT) else export GOPATH := $(shell go env GOPATH) diff --git a/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml b/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml index fc08bce37f..bae10b9c07 100644 --- a/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml +++ b/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml @@ -55,6 +55,28 @@ spec: items: type: string type: array + aws: + description: AWS configures aspects of the ROSA HCP workload cluster + that are specific to AWS. + properties: + privateLink: + description: PrivateLink configures whether Private Link is enabled + for the cluster + type: boolean + privateLinkConfiguration: + description: PrivateLinkConfiguration configures the Private Link + for the cluster + properties: + principals: + description: Principals are the ARNs for principals that are + allowed for the Private Link. + items: + type: string + type: array + type: object + required: + - privateLink + type: object controlPlaneEndpoint: description: ControlPlaneEndpoint represents the endpoint used to communicate with the control plane. @@ -277,6 +299,7 @@ spec: required: - accountID - availabilityZones + - aws - creatorARN - installerRoleARN - machineCIDR diff --git a/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go b/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go index acc5c8e625..4c3c4159f3 100644 --- a/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go +++ b/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go @@ -74,6 +74,22 @@ type RosaControlPlaneSpec struct { //nolint: maligned // - ocmApiUrl: Optional, defaults to 'https://api.openshift.com' // +optional CredentialsSecretRef *corev1.LocalObjectReference `json:"credentialsSecretRef,omitempty"` + + // AWS configures aspects of the ROSA HCP workload cluster that are specific to AWS. + AWS AWSConfiguration `json:"aws"` +} + +type AWSConfiguration struct { + // PrivateLink configures whether Private Link is enabled for the cluster + PrivateLink bool `json:"privateLink"` + + // PrivateLinkConfiguration configures the Private Link for the cluster + PrivateLinkConfiguration *PrivateLinkConfiguration `json:"privateLinkConfiguration,omitempty"` +} + +type PrivateLinkConfiguration struct { + // Principals are the ARNs for principals that are allowed for the Private Link. + Principals []string `json:"principals,omitempty"` } // AWSRolesRef contains references to various AWS IAM roles required for operators to make calls against the AWS API. diff --git a/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go b/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go index 39ebe113b7..6446fcfac4 100644 --- a/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go +++ b/controlplane/rosa/api/v1beta2/zz_generated.deepcopy.go @@ -26,6 +26,26 @@ import ( "sigs.k8s.io/cluster-api/api/v1beta1" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AWSConfiguration) DeepCopyInto(out *AWSConfiguration) { + *out = *in + if in.PrivateLinkConfiguration != nil { + in, out := &in.PrivateLinkConfiguration, &out.PrivateLinkConfiguration + *out = new(PrivateLinkConfiguration) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AWSConfiguration. +func (in *AWSConfiguration) DeepCopy() *AWSConfiguration { + if in == nil { + return nil + } + out := new(AWSConfiguration) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AWSRolesRef) DeepCopyInto(out *AWSRolesRef) { *out = *in @@ -41,6 +61,26 @@ func (in *AWSRolesRef) DeepCopy() *AWSRolesRef { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PrivateLinkConfiguration) DeepCopyInto(out *PrivateLinkConfiguration) { + *out = *in + if in.Principals != nil { + in, out := &in.Principals, &out.Principals + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrivateLinkConfiguration. +func (in *PrivateLinkConfiguration) DeepCopy() *PrivateLinkConfiguration { + if in == nil { + return nil + } + out := new(PrivateLinkConfiguration) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ROSAControlPlane) DeepCopyInto(out *ROSAControlPlane) { *out = *in @@ -165,6 +205,7 @@ func (in *RosaControlPlaneSpec) DeepCopyInto(out *RosaControlPlaneSpec) { *out = new(v1.LocalObjectReference) **out = **in } + in.AWS.DeepCopyInto(&out.AWS) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RosaControlPlaneSpec. diff --git a/controlplane/rosa/controllers/rosacontrolplane_controller.go b/controlplane/rosa/controllers/rosacontrolplane_controller.go index 4bc54d5e7c..547a0c1a62 100644 --- a/controlplane/rosa/controllers/rosacontrolplane_controller.go +++ b/controlplane/rosa/controllers/rosacontrolplane_controller.go @@ -272,6 +272,21 @@ func ocmCluster(controlPlane *rosacontrolplanev1.ROSAControlPlane, now func() ti AccountID(*controlPlane.Spec.AccountID). BillingAccountID(*controlPlane.Spec.AccountID). SubnetIDs(controlPlane.Spec.Subnets...). + PrivateLink(controlPlane.Spec.AWS.PrivateLink). + PrivateLinkConfiguration( + clustersmgmtv1.NewPrivateLinkClusterConfiguration(). + Principals( + func(aws rosacontrolplanev1.AWSConfiguration) []*clustersmgmtv1.PrivateLinkPrincipalBuilder { + var out []*clustersmgmtv1.PrivateLinkPrincipalBuilder + if aws.PrivateLinkConfiguration != nil { + for _, principal := range aws.PrivateLinkConfiguration.Principals { + out = append(out, clustersmgmtv1.NewPrivateLinkPrincipal().Principal(principal)) + } + } + return out + }(controlPlane.Spec.AWS)..., + ), + ). STS( clustersmgmtv1.NewSTS(). RoleARN(*controlPlane.Spec.InstallerRoleARN). diff --git a/controlplane/rosa/controllers/rosacontrolplane_controller_test.go b/controlplane/rosa/controllers/rosacontrolplane_controller_test.go index 95e53c5aaa..5b886e2906 100644 --- a/controlplane/rosa/controllers/rosacontrolplane_controller_test.go +++ b/controlplane/rosa/controllers/rosacontrolplane_controller_test.go @@ -58,6 +58,12 @@ func TestOCMCluster(t *testing.T) { CredentialsSecretRef: &corev1.LocalObjectReference{ Name: "credentials-secret-name", }, + AWS: rosacontrolplanev1beta2.AWSConfiguration{ + PrivateLink: true, + PrivateLinkConfiguration: &rosacontrolplanev1beta2.PrivateLinkConfiguration{ + Principals: []string{"principal-arn-1", "principal-arn-2"}, + }, + }, }, }, }, diff --git a/controlplane/rosa/controllers/testdata/zz_fixture_TestOCMCluster.json b/controlplane/rosa/controllers/testdata/zz_fixture_TestOCMCluster.json index 3e3f4c3a70..66efaa18b6 100644 --- a/controlplane/rosa/controllers/testdata/zz_fixture_TestOCMCluster.json +++ b/controlplane/rosa/controllers/testdata/zz_fixture_TestOCMCluster.json @@ -56,6 +56,19 @@ }, "account_id": "account-id", "billing_account_id": "account-id", + "private_link": true, + "private_link_configuration": { + "principals": [ + { + "kind": "PrivateLinkPrincipal", + "principal": "principal-arn-1" + }, + { + "kind": "PrivateLinkPrincipal", + "principal": "principal-arn-2" + } + ] + }, "subnet_ids": [ "subnet-1", "subnet-2"