diff --git a/cmd/aro/rp.go b/cmd/aro/rp.go index 5dfd3eb7f2d..99accccecc6 100644 --- a/cmd/aro/rp.go +++ b/cmd/aro/rp.go @@ -163,6 +163,11 @@ func rp(ctx context.Context, log, audit *logrus.Entry) error { return err } + dbPlatformWorkloadIdentityRoleSets, err := database.NewPlatformWorkloadIdentityRoleSets(ctx, dbc, dbName) + if err != nil { + return err + } + go database.EmitMetrics(ctx, log, dbOpenShiftClusters, metrics) feAead, err := encryption.NewMulti(ctx, _env.ServiceKeyvault(), env.FrontendEncryptionSecretV2Name, env.FrontendEncryptionSecretName) @@ -173,7 +178,7 @@ func rp(ctx context.Context, log, audit *logrus.Entry) error { if err != nil { return err } - f, err := frontend.NewFrontend(ctx, audit, log.WithField("component", "frontend"), _env, dbAsyncOperations, dbClusterManagerConfiguration, dbOpenShiftClusters, dbSubscriptions, dbOpenShiftVersions, api.APIs, metrics, clusterm, feAead, hiveClusterManager, adminactions.NewKubeActions, adminactions.NewAzureActions, clusterdata.NewParallelEnricher(metrics, _env)) + f, err := frontend.NewFrontend(ctx, audit, log.WithField("component", "frontend"), _env, dbAsyncOperations, dbClusterManagerConfiguration, dbOpenShiftClusters, dbSubscriptions, dbOpenShiftVersions, dbPlatformWorkloadIdentityRoleSets, api.APIs, metrics, clusterm, feAead, hiveClusterManager, adminactions.NewKubeActions, adminactions.NewAzureActions, clusterdata.NewParallelEnricher(metrics, _env)) if err != nil { return err } diff --git a/pkg/api/admin/platformworkloadidentityroleset.go b/pkg/api/admin/platformworkloadidentityroleset.go index 9ed4680bceb..d2b26e9a6fb 100644 --- a/pkg/api/admin/platformworkloadidentityroleset.go +++ b/pkg/api/admin/platformworkloadidentityroleset.go @@ -33,14 +33,14 @@ type PlatformWorkloadIdentityRoleSetProperties struct { // PlatformWorkloadIdentityRole represents a mapping from a particular OCP operator to the built-in role that should be assigned to that operator's corresponding managed identity. type PlatformWorkloadIdentityRole struct { // OperatorName represents the name of the operator that this role is for. - OperatorName string `json:"operatorName,omitempty" mutable:"true"` + OperatorName string `json:"operatorName,omitempty" mutable:"true" validate:"required"` // RoleDefinitionName represents the name of the role. - RoleDefinitionName string `json:"roleDefinitionName,omitempty" mutable:"true"` + RoleDefinitionName string `json:"roleDefinitionName,omitempty" mutable:"true" validate:"required"` // RoleDefinitionID represents the resource ID of the role definition. - RoleDefinitionID string `json:"roleDefinitionId,omitempty" mutable:"true"` + RoleDefinitionID string `json:"roleDefinitionId,omitempty" mutable:"true" validate:"required"` // ServiceAccounts represents the set of service accounts associated with the given operator, since each service account needs its own federated credential. - ServiceAccounts []string `json:"serviceAccounts,omitempty" mutable:"true"` + ServiceAccounts []string `json:"serviceAccounts,omitempty" mutable:"true" validate:"required"` } diff --git a/pkg/api/admin/platformworkloadidentityroleset_convert.go b/pkg/api/admin/platformworkloadidentityroleset_convert.go index b1caa21cb63..b7bf175b1e6 100644 --- a/pkg/api/admin/platformworkloadidentityroleset_convert.go +++ b/pkg/api/admin/platformworkloadidentityroleset_convert.go @@ -1,11 +1,10 @@ package admin +import "github.com/Azure/ARO-RP/pkg/api" + // Copyright (c) Microsoft Corporation. // Licensed under the Apache License 2.0. -/* -TODO: Uncomment once API endpoints have been implemented and this code is being used. - type platformWorkloadIdentityRoleSetConverter struct{} // platformWorkloadIdentityRoleSetConverter.ToExternal returns a new external representation @@ -21,12 +20,17 @@ func (c platformWorkloadIdentityRoleSetConverter) ToExternal(s *api.PlatformWork }, } - for i, r := range s.Properties.PlatformWorkloadIdentityRoles { - out.Properties.PlatformWorkloadIdentityRoles[i].OperatorName = r.OperatorName - out.Properties.PlatformWorkloadIdentityRoles[i].RoleDefinitionName = r.RoleDefinitionName - out.Properties.PlatformWorkloadIdentityRoles[i].RoleDefinitionID = r.RoleDefinitionID - out.Properties.PlatformWorkloadIdentityRoles[i].ServiceAccounts = make([]string, 0, len(r.ServiceAccounts)) - out.Properties.PlatformWorkloadIdentityRoles[i].ServiceAccounts = append(out.Properties.PlatformWorkloadIdentityRoles[i].ServiceAccounts, r.ServiceAccounts...) + for _, r := range s.Properties.PlatformWorkloadIdentityRoles { + role := PlatformWorkloadIdentityRole{ + OperatorName: r.OperatorName, + RoleDefinitionName: r.RoleDefinitionName, + RoleDefinitionID: r.RoleDefinitionID, + ServiceAccounts: make([]string, 0, len(r.ServiceAccounts)), + } + + role.ServiceAccounts = append(role.ServiceAccounts, r.ServiceAccounts...) + + out.Properties.PlatformWorkloadIdentityRoles = append(out.Properties.PlatformWorkloadIdentityRoles, role) } return out @@ -56,12 +60,16 @@ func (c platformWorkloadIdentityRoleSetConverter) ToInternal(_new interface{}, o out.Properties.OpenShiftVersion = new.Properties.OpenShiftVersion out.Properties.PlatformWorkloadIdentityRoles = make([]api.PlatformWorkloadIdentityRole, 0, len(new.Properties.PlatformWorkloadIdentityRoles)) - for i, r := range new.Properties.PlatformWorkloadIdentityRoles { - out.Properties.PlatformWorkloadIdentityRoles[i].OperatorName = r.OperatorName - out.Properties.PlatformWorkloadIdentityRoles[i].RoleDefinitionName = r.RoleDefinitionName - out.Properties.PlatformWorkloadIdentityRoles[i].RoleDefinitionID = r.RoleDefinitionID - out.Properties.PlatformWorkloadIdentityRoles[i].ServiceAccounts = make([]string, 0, len(r.ServiceAccounts)) - out.Properties.PlatformWorkloadIdentityRoles[i].ServiceAccounts = append(out.Properties.PlatformWorkloadIdentityRoles[i].ServiceAccounts, r.ServiceAccounts...) + for _, r := range new.Properties.PlatformWorkloadIdentityRoles { + role := api.PlatformWorkloadIdentityRole{ + OperatorName: r.OperatorName, + RoleDefinitionName: r.RoleDefinitionName, + RoleDefinitionID: r.RoleDefinitionID, + ServiceAccounts: make([]string, 0, len(r.ServiceAccounts)), + } + + role.ServiceAccounts = append(role.ServiceAccounts, r.ServiceAccounts...) + + out.Properties.PlatformWorkloadIdentityRoles = append(out.Properties.PlatformWorkloadIdentityRoles, role) } } -*/ diff --git a/pkg/api/admin/platformworkloadidentityroleset_validatestatic.go b/pkg/api/admin/platformworkloadidentityroleset_validatestatic.go index e70bffc0555..d6fcfb9c1b6 100644 --- a/pkg/api/admin/platformworkloadidentityroleset_validatestatic.go +++ b/pkg/api/admin/platformworkloadidentityroleset_validatestatic.go @@ -1,11 +1,17 @@ package admin +import ( + "fmt" + "net/http" + "strings" + + "github.com/Azure/ARO-RP/pkg/api" + "github.com/Azure/ARO-RP/pkg/api/util/immutable" +) + // Copyright (c) Microsoft Corporation. // Licensed under the Apache License 2.0. -/* -TODO: Uncomment once API endpoints have been implemented and this code is being used. - type platformWorkloadIdentityRoleSetStaticValidator struct{} func (sv platformWorkloadIdentityRoleSetStaticValidator) Static(_new interface{}, _current *api.PlatformWorkloadIdentityRoleSet) error { @@ -37,27 +43,31 @@ func (sv platformWorkloadIdentityRoleSetStaticValidator) validate(new *PlatformW return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, "properties.platformWorkloadIdentityRoles", "Must be provided and must be non-empty") } - errs := []error{} + missingProperties := []string{} for i, r := range new.Properties.PlatformWorkloadIdentityRoles { if r.OperatorName == "" { - errs = append(errs, api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, fmt.Sprintf("properties.platformWorkloadIdentityRoles[%d].operatorName", i), "Must be provided")) + missingProperties = append(missingProperties, fmt.Sprintf("properties.platformWorkloadIdentityRoles[%d].operatorName", i)) } if r.RoleDefinitionName == "" { - errs = append(errs, api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, fmt.Sprintf("properties.platformWorkloadIdentityRoles[%d].roleDefinitionName", i), "Must be provided")) + missingProperties = append(missingProperties, fmt.Sprintf("properties.platformWorkloadIdentityRoles[%d].roleDefinitionName", i)) } if r.RoleDefinitionID == "" { - errs = append(errs, api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, fmt.Sprintf("properties.platformWorkloadIdentityRoles[%d].roleDefinitionId", i), "Must be provided")) + missingProperties = append(missingProperties, fmt.Sprintf("properties.platformWorkloadIdentityRoles[%d].roleDefinitionId", i)) } if r.ServiceAccounts == nil || len(r.ServiceAccounts) == 0 { - errs = append(errs, api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, fmt.Sprintf("properties.platformWorkloadIdentityRoles[%d].serviceAccounts", i), "Must be provided and must be non-empty")) + missingProperties = append(missingProperties, fmt.Sprintf("properties.platformWorkloadIdentityRoles[%d].serviceAccounts", i)) } } - return errors.Join(errs...) + if len(missingProperties) > 0 { + return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, strings.Join(missingProperties, ", "), "Must be provided") + } + + return nil } func (sv platformWorkloadIdentityRoleSetStaticValidator) validateDelta(new, current *PlatformWorkloadIdentityRoleSet) error { @@ -68,4 +78,3 @@ func (sv platformWorkloadIdentityRoleSetStaticValidator) validateDelta(new, curr } return nil } -*/ diff --git a/pkg/api/admin/register.go b/pkg/api/admin/register.go index ac29676945f..4a0613c276e 100644 --- a/pkg/api/admin/register.go +++ b/pkg/api/admin/register.go @@ -12,9 +12,11 @@ const APIVersion = "admin" func init() { api.APIs[APIVersion] = &api.Version{ - OpenShiftClusterConverter: openShiftClusterConverter{}, - OpenShiftClusterStaticValidator: openShiftClusterStaticValidator{}, - OpenShiftVersionConverter: openShiftVersionConverter{}, - OpenShiftVersionStaticValidator: openShiftVersionStaticValidator{}, + OpenShiftClusterConverter: openShiftClusterConverter{}, + OpenShiftClusterStaticValidator: openShiftClusterStaticValidator{}, + OpenShiftVersionConverter: openShiftVersionConverter{}, + OpenShiftVersionStaticValidator: openShiftVersionStaticValidator{}, + PlatformWorkloadIdentityRoleSetConverter: platformWorkloadIdentityRoleSetConverter{}, + PlatformWorkloadIdentityRoleSetStaticValidator: platformWorkloadIdentityRoleSetStaticValidator{}, } } diff --git a/pkg/api/register.go b/pkg/api/register.go index 89b986ac55d..5e91693909b 100644 --- a/pkg/api/register.go +++ b/pkg/api/register.go @@ -37,6 +37,16 @@ type OpenShiftVersionStaticValidator interface { Static(interface{}, *OpenShiftVersion) error } +type PlatformWorkloadIdentityRoleSetConverter interface { + ToExternal(*PlatformWorkloadIdentityRoleSet) interface{} + ToExternalList([]*PlatformWorkloadIdentityRoleSet) interface{} + ToInternal(interface{}, *PlatformWorkloadIdentityRoleSet) +} + +type PlatformWorkloadIdentityRoleSetStaticValidator interface { + Static(interface{}, *PlatformWorkloadIdentityRoleSet) error +} + type SyncSetConverter interface { ToExternal(*SyncSet) interface{} ToExternalList([]*SyncSet) interface{} @@ -63,18 +73,20 @@ type SecretConverter interface { // Version is a set of endpoints implemented by each API version type Version struct { - OpenShiftClusterConverter OpenShiftClusterConverter - OpenShiftClusterStaticValidator OpenShiftClusterStaticValidator - OpenShiftClusterCredentialsConverter OpenShiftClusterCredentialsConverter - OpenShiftClusterAdminKubeconfigConverter OpenShiftClusterAdminKubeconfigConverter - OpenShiftVersionConverter OpenShiftVersionConverter - OpenShiftVersionStaticValidator OpenShiftVersionStaticValidator - OperationList OperationList - SyncSetConverter SyncSetConverter - MachinePoolConverter MachinePoolConverter - SyncIdentityProviderConverter SyncIdentityProviderConverter - SecretConverter SecretConverter - ClusterManagerStaticValidator ClusterManagerStaticValidator + OpenShiftClusterConverter OpenShiftClusterConverter + OpenShiftClusterStaticValidator OpenShiftClusterStaticValidator + OpenShiftClusterCredentialsConverter OpenShiftClusterCredentialsConverter + OpenShiftClusterAdminKubeconfigConverter OpenShiftClusterAdminKubeconfigConverter + OpenShiftVersionConverter OpenShiftVersionConverter + OpenShiftVersionStaticValidator OpenShiftVersionStaticValidator + PlatformWorkloadIdentityRoleSetConverter PlatformWorkloadIdentityRoleSetConverter + PlatformWorkloadIdentityRoleSetStaticValidator PlatformWorkloadIdentityRoleSetStaticValidator + OperationList OperationList + SyncSetConverter SyncSetConverter + MachinePoolConverter MachinePoolConverter + SyncIdentityProviderConverter SyncIdentityProviderConverter + SecretConverter SecretConverter + ClusterManagerStaticValidator ClusterManagerStaticValidator } // APIs is the map of registered API versions diff --git a/pkg/api/v20240812preview/platformworkloadidentityroleset_convert.go b/pkg/api/v20240812preview/platformworkloadidentityroleset_convert.go index a651229c16f..ffd92a6ef05 100644 --- a/pkg/api/v20240812preview/platformworkloadidentityroleset_convert.go +++ b/pkg/api/v20240812preview/platformworkloadidentityroleset_convert.go @@ -24,10 +24,13 @@ func (c platformWorkloadIdentityRoleSetConverter) ToExternal(s *api.PlatformWork }, } - for i, r := range s.Properties.PlatformWorkloadIdentityRoles { - out.Properties.PlatformWorkloadIdentityRoles[i].OperatorName = r.OperatorName - out.Properties.PlatformWorkloadIdentityRoles[i].RoleDefinitionName = r.RoleDefinitionName - out.Properties.PlatformWorkloadIdentityRoles[i].RoleDefinitionID = r.RoleDefinitionID + for _, r := range s.Properties.PlatformWorkloadIdentityRoles { + role := PlatformWorkloadIdentityRole{ + OperatorName: r.OperatorName, + RoleDefinitionName: r.RoleDefinitionName, + RoleDefinitionID: r.RoleDefinitionID, + } + out.Properties.PlatformWorkloadIdentityRoles = append(out.Properties.PlatformWorkloadIdentityRoles, role) } return out @@ -57,9 +60,12 @@ func (c platformWorkloadIdentityRoleSetConverter) ToInternal(_new interface{}, o out.Properties.OpenShiftVersion = new.Properties.OpenShiftVersion out.Properties.PlatformWorkloadIdentityRoles = make([]api.PlatformWorkloadIdentityRole, 0, len(new.Properties.PlatformWorkloadIdentityRoles)) - for i, r := range new.Properties.PlatformWorkloadIdentityRoles { - out.Properties.PlatformWorkloadIdentityRoles[i].OperatorName = r.OperatorName - out.Properties.PlatformWorkloadIdentityRoles[i].RoleDefinitionName = r.RoleDefinitionName - out.Properties.PlatformWorkloadIdentityRoles[i].RoleDefinitionID = r.RoleDefinitionID + for _, r := range new.Properties.PlatformWorkloadIdentityRoles { + role := api.PlatformWorkloadIdentityRole{ + OperatorName: r.OperatorName, + RoleDefinitionName: r.RoleDefinitionName, + RoleDefinitionID: r.RoleDefinitionID, + } + out.Properties.PlatformWorkloadIdentityRoles = append(out.Properties.PlatformWorkloadIdentityRoles, role) } } diff --git a/pkg/api/v20240812preview/register.go b/pkg/api/v20240812preview/register.go index 2083e837d59..51d13fe8f60 100644 --- a/pkg/api/v20240812preview/register.go +++ b/pkg/api/v20240812preview/register.go @@ -22,6 +22,7 @@ func init() { OpenShiftClusterCredentialsConverter: openShiftClusterCredentialsConverter{}, OpenShiftClusterAdminKubeconfigConverter: openShiftClusterAdminKubeconfigConverter{}, OpenShiftVersionConverter: openShiftVersionConverter{}, + PlatformWorkloadIdentityRoleSetConverter: platformWorkloadIdentityRoleSetConverter{}, OperationList: api.OperationList{ Operations: []api.Operation{ api.OperationResultsRead, diff --git a/pkg/frontend/admin_hive_clusterdeployment_get_test.go b/pkg/frontend/admin_hive_clusterdeployment_get_test.go index cbaa7063fa6..4e40e4516d8 100644 --- a/pkg/frontend/admin_hive_clusterdeployment_get_test.go +++ b/pkg/frontend/admin_hive_clusterdeployment_get_test.go @@ -90,10 +90,10 @@ func Test_getAdminHiveClusterDeployment(t *testing.T) { clusterManager := mock_hive.NewMockClusterManager(controller) clusterManager.EXPECT().GetClusterDeployment(gomock.Any(), gomock.Any()).Return(&clusterDeployment, nil).Times(tt.expectedGetClusterDeploymentCallCount) f, err = NewFrontend(ctx, ti.audit, ti.log, _env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, - ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, clusterManager, nil, nil, nil) + ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, clusterManager, nil, nil, nil) } else { f, err = NewFrontend(ctx, ti.audit, ti.log, _env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, - ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) } if err != nil { diff --git a/pkg/frontend/admin_openshiftcluster_approvecsr_test.go b/pkg/frontend/admin_openshiftcluster_approvecsr_test.go index 3bef4853bad..53b76de1753 100644 --- a/pkg/frontend/admin_openshiftcluster_approvecsr_test.go +++ b/pkg/frontend/admin_openshiftcluster_approvecsr_test.go @@ -91,7 +91,7 @@ func TestAdminApproveCSR(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) { + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) { return k, nil }, nil, nil) diff --git a/pkg/frontend/admin_openshiftcluster_cordonnode_test.go b/pkg/frontend/admin_openshiftcluster_cordonnode_test.go index d524af4a757..52097ef559c 100644 --- a/pkg/frontend/admin_openshiftcluster_cordonnode_test.go +++ b/pkg/frontend/admin_openshiftcluster_cordonnode_test.go @@ -152,7 +152,7 @@ func TestAdminCordonUncordonNode(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) { + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) { return k, nil }, nil, nil) diff --git a/pkg/frontend/admin_openshiftcluster_delete_managedresource_test.go b/pkg/frontend/admin_openshiftcluster_delete_managedresource_test.go index 4215af7ceaa..3fd49d5b22a 100644 --- a/pkg/frontend/admin_openshiftcluster_delete_managedresource_test.go +++ b/pkg/frontend/admin_openshiftcluster_delete_managedresource_test.go @@ -113,7 +113,7 @@ func TestAdminDeleteManagedResource(t *testing.T) { a := mock_adminactions.NewMockAzureActions(ti.controller) tt.mocks(tt, a) - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { return a, nil }, nil) diff --git a/pkg/frontend/admin_openshiftcluster_drainnode_test.go b/pkg/frontend/admin_openshiftcluster_drainnode_test.go index b7d94da3587..c23a6b968b8 100644 --- a/pkg/frontend/admin_openshiftcluster_drainnode_test.go +++ b/pkg/frontend/admin_openshiftcluster_drainnode_test.go @@ -84,7 +84,7 @@ func TestAdminDrainNode(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) { + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) { return k, nil }, nil, nil) diff --git a/pkg/frontend/admin_openshiftcluster_etcdcertificaterenew_test.go b/pkg/frontend/admin_openshiftcluster_etcdcertificaterenew_test.go index a26cf2d1d2d..ab58f9615cd 100644 --- a/pkg/frontend/admin_openshiftcluster_etcdcertificaterenew_test.go +++ b/pkg/frontend/admin_openshiftcluster_etcdcertificaterenew_test.go @@ -536,6 +536,7 @@ func TestAdminEtcdCertificateRenew(t *testing.T) { ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, + nil, api.APIs, &noop.Noop{}, &noop.Noop{}, @@ -753,6 +754,7 @@ func TestAdminEtcdCertificateRecovery(t *testing.T) { ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, + nil, api.APIs, &noop.Noop{}, &noop.Noop{}, diff --git a/pkg/frontend/admin_openshiftcluster_etcdrecovery_test.go b/pkg/frontend/admin_openshiftcluster_etcdrecovery_test.go index f2f405340ef..2e15ccb7e35 100644 --- a/pkg/frontend/admin_openshiftcluster_etcdrecovery_test.go +++ b/pkg/frontend/admin_openshiftcluster_etcdrecovery_test.go @@ -167,6 +167,7 @@ func TestAdminEtcdRecovery(t *testing.T) { ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, + nil, api.APIs, &noop.Noop{}, &noop.Noop{}, diff --git a/pkg/frontend/admin_openshiftcluster_kubernetesobjects_test.go b/pkg/frontend/admin_openshiftcluster_kubernetesobjects_test.go index 604f064b31c..b0b2e06bd75 100644 --- a/pkg/frontend/admin_openshiftcluster_kubernetesobjects_test.go +++ b/pkg/frontend/admin_openshiftcluster_kubernetesobjects_test.go @@ -262,7 +262,7 @@ func TestAdminKubernetesObjectsGetAndDelete(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) { + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) { return k, nil }, nil, nil) if err != nil { @@ -411,7 +411,7 @@ func TestAdminPostKubernetesObjects(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) { + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) { return k, nil }, nil, nil) if err != nil { diff --git a/pkg/frontend/admin_openshiftcluster_kubernetespods_logs_test.go b/pkg/frontend/admin_openshiftcluster_kubernetespods_logs_test.go index 91b84279719..8dd8c53b563 100644 --- a/pkg/frontend/admin_openshiftcluster_kubernetespods_logs_test.go +++ b/pkg/frontend/admin_openshiftcluster_kubernetespods_logs_test.go @@ -125,7 +125,7 @@ func TestAdminKubernetesGetPodLogs(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) { + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster) (adminactions.KubeActions, error) { return k, nil }, nil, nil) if err != nil { diff --git a/pkg/frontend/admin_openshiftcluster_list_test.go b/pkg/frontend/admin_openshiftcluster_list_test.go index c39934e30ef..27922d2a433 100644 --- a/pkg/frontend/admin_openshiftcluster_list_test.go +++ b/pkg/frontend/admin_openshiftcluster_list_test.go @@ -124,7 +124,7 @@ func TestAdminListOpenShiftCluster(t *testing.T) { ti.openShiftClustersClient.SetError(tt.throwsError) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, aead, nil, nil, nil, ti.enricher) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, aead, nil, nil, nil, ti.enricher) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/admin_openshiftcluster_redeployvm_test.go b/pkg/frontend/admin_openshiftcluster_redeployvm_test.go index fef1bdcf051..818dcea16e7 100644 --- a/pkg/frontend/admin_openshiftcluster_redeployvm_test.go +++ b/pkg/frontend/admin_openshiftcluster_redeployvm_test.go @@ -84,7 +84,7 @@ func TestAdminRedeployVM(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { return a, nil }, nil) diff --git a/pkg/frontend/admin_openshiftcluster_resources_list_test.go b/pkg/frontend/admin_openshiftcluster_resources_list_test.go index 0fdc9a64f8f..bb56f0bebd3 100644 --- a/pkg/frontend/admin_openshiftcluster_resources_list_test.go +++ b/pkg/frontend/admin_openshiftcluster_resources_list_test.go @@ -93,7 +93,7 @@ func TestAdminListResourcesList(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { return a, nil }, nil) mockResponder := mock_frontend.NewMockStreamResponder(ti.controller) diff --git a/pkg/frontend/admin_openshiftcluster_startvm_test.go b/pkg/frontend/admin_openshiftcluster_startvm_test.go index cf0eadb9f01..e7a91708bd4 100644 --- a/pkg/frontend/admin_openshiftcluster_startvm_test.go +++ b/pkg/frontend/admin_openshiftcluster_startvm_test.go @@ -84,7 +84,7 @@ func TestAdminStartVM(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { return a, nil }, nil) diff --git a/pkg/frontend/admin_openshiftcluster_stopvm_test.go b/pkg/frontend/admin_openshiftcluster_stopvm_test.go index 77fcd351d28..bb35093ed84 100644 --- a/pkg/frontend/admin_openshiftcluster_stopvm_test.go +++ b/pkg/frontend/admin_openshiftcluster_stopvm_test.go @@ -86,7 +86,7 @@ func TestAdminStopVM(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { return a, nil }, nil) diff --git a/pkg/frontend/admin_openshiftcluster_vmresize_test.go b/pkg/frontend/admin_openshiftcluster_vmresize_test.go index 0d5946f31eb..6eaa65f9acd 100644 --- a/pkg/frontend/admin_openshiftcluster_vmresize_test.go +++ b/pkg/frontend/admin_openshiftcluster_vmresize_test.go @@ -202,7 +202,7 @@ func TestAdminVMResize(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { return a, nil }, nil) diff --git a/pkg/frontend/admin_openshiftcluster_vmsizelist_test.go b/pkg/frontend/admin_openshiftcluster_vmsizelist_test.go index 77fa4ea51b8..1d7fda2cabd 100644 --- a/pkg/frontend/admin_openshiftcluster_vmsizelist_test.go +++ b/pkg/frontend/admin_openshiftcluster_vmsizelist_test.go @@ -136,7 +136,7 @@ func TestAdminListVMSizeList(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { return a, nil }, nil) diff --git a/pkg/frontend/admin_openshiftversion_list_test.go b/pkg/frontend/admin_openshiftversion_list_test.go index c851c806bb4..ff18ac7aebf 100644 --- a/pkg/frontend/admin_openshiftversion_list_test.go +++ b/pkg/frontend/admin_openshiftversion_list_test.go @@ -110,7 +110,7 @@ func TestOpenShiftVersionList(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, ti.openShiftVersionsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, ti.openShiftVersionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) diff --git a/pkg/frontend/admin_openshiftversion_put_test.go b/pkg/frontend/admin_openshiftversion_put_test.go index e92e10cb909..efef5a07b6a 100644 --- a/pkg/frontend/admin_openshiftversion_put_test.go +++ b/pkg/frontend/admin_openshiftversion_put_test.go @@ -266,7 +266,7 @@ func TestOpenShiftVersionPut(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, ti.openShiftVersionsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, ti.openShiftVersionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/admin_platformworkloadidentityroleset_list.go b/pkg/frontend/admin_platformworkloadidentityroleset_list.go new file mode 100644 index 00000000000..855b808e2e1 --- /dev/null +++ b/pkg/frontend/admin_platformworkloadidentityroleset_list.go @@ -0,0 +1,47 @@ +package frontend + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "encoding/json" + "net/http" + "path/filepath" + "sort" + + "github.com/sirupsen/logrus" + + "github.com/Azure/ARO-RP/pkg/api" + "github.com/Azure/ARO-RP/pkg/api/admin" + "github.com/Azure/ARO-RP/pkg/frontend/middleware" + "github.com/Azure/ARO-RP/pkg/util/version" +) + +func (f *frontend) getAdminPlatformWorkloadIdentityRoleSets(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + log := ctx.Value(middleware.ContextKeyLog).(*logrus.Entry) + r.URL.Path = filepath.Dir(r.URL.Path) + + converter := f.apis[admin.APIVersion].PlatformWorkloadIdentityRoleSetConverter + + docs, err := f.dbPlatformWorkloadIdentityRoleSets.ListAll(ctx) + if err != nil { + log.Error(err) + api.WriteError(w, http.StatusInternalServerError, api.CloudErrorCodeInternalServerError, "", "Internal server error.") + return + } + + var roleSets []*api.PlatformWorkloadIdentityRoleSet + if docs != nil { + for _, doc := range docs.PlatformWorkloadIdentityRoleSetDocuments { + roleSets = append(roleSets, doc.PlatformWorkloadIdentityRoleSet) + } + } + + sort.Slice(roleSets, func(i, j int) bool { + return version.CreateSemverFromMinorVersionString(roleSets[i].Properties.OpenShiftVersion).LessThan(*version.CreateSemverFromMinorVersionString(roleSets[j].Properties.OpenShiftVersion)) + }) + + b, err := json.MarshalIndent(converter.ToExternalList(roleSets), "", " ") + adminReply(log, w, nil, b, err) +} diff --git a/pkg/frontend/admin_platformworkloadidentityroleset_list_test.go b/pkg/frontend/admin_platformworkloadidentityroleset_list_test.go new file mode 100644 index 00000000000..4f92a3be110 --- /dev/null +++ b/pkg/frontend/admin_platformworkloadidentityroleset_list_test.go @@ -0,0 +1,281 @@ +package frontend + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "context" + "errors" + "net/http" + "sort" + "testing" + + "github.com/Azure/ARO-RP/pkg/api" + "github.com/Azure/ARO-RP/pkg/api/admin" + "github.com/Azure/ARO-RP/pkg/database/cosmosdb" + "github.com/Azure/ARO-RP/pkg/metrics/noop" + "github.com/Azure/ARO-RP/pkg/util/version" + testdatabase "github.com/Azure/ARO-RP/test/database" +) + +func TestPlatformWorkloadIdentityRoleSetList(t *testing.T) { + ctx := context.Background() + + type test struct { + name string + fixture func(f *testdatabase.Fixture) + cosmosdb func(c *cosmosdb.FakePlatformWorkloadIdentityRoleSetDocumentClient) + wantStatusCode int + wantResponse *admin.PlatformWorkloadIdentityRoleSetList + wantError string + } + + for _, tt := range []*test{ + { + name: "GET request returns empty result with StatusOK", + fixture: func(f *testdatabase.Fixture) {}, + wantStatusCode: http.StatusOK, + wantResponse: &admin.PlatformWorkloadIdentityRoleSetList{ + PlatformWorkloadIdentityRoleSets: []*admin.PlatformWorkloadIdentityRoleSet{}, + }, + }, + { + name: "GET request returns non-empty result with StatusOK", + fixture: func(f *testdatabase.Fixture) { + f.AddPlatformWorkloadIdentityRoleSetDocuments( + &api.PlatformWorkloadIdentityRoleSetDocument{ + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + &api.PlatformWorkloadIdentityRoleSetDocument{ + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.15", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + }, + }, + }, + }, + ) + }, + wantStatusCode: http.StatusOK, + wantResponse: &admin.PlatformWorkloadIdentityRoleSetList{ + PlatformWorkloadIdentityRoleSets: []*admin.PlatformWorkloadIdentityRoleSet{ + { + Properties: admin.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []admin.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + { + Properties: admin.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.15", + PlatformWorkloadIdentityRoles: []admin.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "GET request with StatusOK returns results in correct order even if Cosmos DB returns them in a different order", + fixture: func(f *testdatabase.Fixture) { + f.AddPlatformWorkloadIdentityRoleSetDocuments( + &api.PlatformWorkloadIdentityRoleSetDocument{ + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + &api.PlatformWorkloadIdentityRoleSetDocument{ + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.15", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + }, + }, + }, + }, + ) + }, + cosmosdb: func(c *cosmosdb.FakePlatformWorkloadIdentityRoleSetDocumentClient) { + // Sort the documents in descending order rather than ascending order, which + // is the order we expect to see in the response. + c.SetSorter(func(roleSets []*api.PlatformWorkloadIdentityRoleSetDocument) { + sort.Slice(roleSets, func(i, j int) bool { + return version.CreateSemverFromMinorVersionString(roleSets[j].PlatformWorkloadIdentityRoleSet.Properties.OpenShiftVersion).LessThan(*version.CreateSemverFromMinorVersionString(roleSets[i].PlatformWorkloadIdentityRoleSet.Properties.OpenShiftVersion)) + }) + }) + }, + wantStatusCode: http.StatusOK, + wantResponse: &admin.PlatformWorkloadIdentityRoleSetList{ + PlatformWorkloadIdentityRoleSets: []*admin.PlatformWorkloadIdentityRoleSet{ + { + Properties: admin.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []admin.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + { + Properties: admin.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.15", + PlatformWorkloadIdentityRoles: []admin.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "GET request results in StatusInternalServerError due to issues with Cosmos DB", + fixture: func(f *testdatabase.Fixture) {}, + cosmosdb: func(c *cosmosdb.FakePlatformWorkloadIdentityRoleSetDocumentClient) { + c.SetError(errors.New("Well shoot, Cosmos DB isn't working!")) + }, + wantStatusCode: http.StatusInternalServerError, + wantResponse: &admin.PlatformWorkloadIdentityRoleSetList{ + PlatformWorkloadIdentityRoleSets: []*admin.PlatformWorkloadIdentityRoleSet{}, + }, + wantError: api.NewCloudError(http.StatusInternalServerError, api.CloudErrorCodeInternalServerError, "", "Internal server error.").Error(), + }, + } { + t.Run(tt.name, func(t *testing.T) { + ti := newTestInfra(t).WithPlatformWorkloadIdentityRoleSets() + defer ti.done() + + if tt.cosmosdb != nil { + tt.cosmosdb(ti.platformWorkloadIdentityRoleSetsClient) + } + + err := ti.buildFixtures(tt.fixture) + if err != nil { + t.Fatal(err) + } + + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, nil, ti.platformWorkloadIdentityRoleSetsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + + if err != nil { + t.Fatal(err) + } + + go f.Run(ctx, nil, nil) + + resp, b, err := ti.request(http.MethodGet, "https://server/admin/platformworkloadidentityrolesets", + nil, nil) + if err != nil { + t.Fatal(err) + } + + err = validateResponse(resp, b, tt.wantStatusCode, tt.wantError, tt.wantResponse) + if err != nil { + t.Error(err) + } + }) + } +} diff --git a/pkg/frontend/admin_platformworkloadidentityroleset_put.go b/pkg/frontend/admin_platformworkloadidentityroleset_put.go new file mode 100644 index 00000000000..8b11641b4da --- /dev/null +++ b/pkg/frontend/admin_platformworkloadidentityroleset_put.go @@ -0,0 +1,96 @@ +package frontend + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "encoding/json" + "net/http" + "path/filepath" + + "github.com/sirupsen/logrus" + + "github.com/Azure/ARO-RP/pkg/api" + "github.com/Azure/ARO-RP/pkg/api/admin" + "github.com/Azure/ARO-RP/pkg/frontend/middleware" +) + +func (f *frontend) putAdminPlatformWorkloadIdentityRoleSet(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + log := ctx.Value(middleware.ContextKeyLog).(*logrus.Entry) + r.URL.Path = filepath.Dir(r.URL.Path) + + converter := f.apis[admin.APIVersion].PlatformWorkloadIdentityRoleSetConverter + staticValidator := f.apis[admin.APIVersion].PlatformWorkloadIdentityRoleSetStaticValidator + + body := r.Context().Value(middleware.ContextKeyBody).([]byte) + if len(body) == 0 || !json.Valid(body) { + api.WriteError(w, http.StatusBadRequest, api.CloudErrorCodeInvalidRequestContent, "", "The request content was invalid and could not be deserialized.") + return + } + + var ext *admin.PlatformWorkloadIdentityRoleSet + err := json.Unmarshal(body, &ext) + if err != nil { + api.WriteError(w, http.StatusBadRequest, api.CloudErrorCodeInvalidRequestContent, "", "The request content could not be deserialized: "+err.Error()) + return + } + + docs, err := f.dbPlatformWorkloadIdentityRoleSets.ListAll(ctx) + if err != nil { + log.Error(err) + api.WriteError(w, http.StatusInternalServerError, api.CloudErrorCodeInternalServerError, "", "Internal server error.") + return + } + + var roleSetDoc *api.PlatformWorkloadIdentityRoleSetDocument + if docs != nil { + for _, doc := range docs.PlatformWorkloadIdentityRoleSetDocuments { + if doc.PlatformWorkloadIdentityRoleSet.Properties.OpenShiftVersion == ext.Properties.OpenShiftVersion { + roleSetDoc = doc + break + } + } + } + + isCreate := roleSetDoc == nil + if isCreate { + err = staticValidator.Static(ext, nil) + roleSetDoc = &api.PlatformWorkloadIdentityRoleSetDocument{ + ID: f.dbPlatformWorkloadIdentityRoleSets.NewUUID(), + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{}, + } + } else { + err = staticValidator.Static(ext, roleSetDoc.PlatformWorkloadIdentityRoleSet) + } + if err != nil { + adminReply(log, w, nil, []byte{}, err) + return + } + + converter.ToInternal(ext, roleSetDoc.PlatformWorkloadIdentityRoleSet) + + if isCreate { + roleSetDoc, err = f.dbPlatformWorkloadIdentityRoleSets.Create(ctx, roleSetDoc) + if err != nil { + log.Error(err) + api.WriteError(w, http.StatusInternalServerError, api.CloudErrorCodeInternalServerError, "", "Internal server error.") + return + } + } else { + roleSetDoc, err = f.dbPlatformWorkloadIdentityRoleSets.Update(ctx, roleSetDoc) + if err != nil { + log.Error(err) + api.WriteError(w, http.StatusInternalServerError, api.CloudErrorCodeInternalServerError, "", "Internal server error.") + return + } + } + + b, err := json.MarshalIndent(converter.ToExternal(roleSetDoc.PlatformWorkloadIdentityRoleSet), "", " ") + if err == nil { + if isCreate { + err = statusCodeError(http.StatusCreated) + } + } + adminReply(log, w, nil, b, err) +} diff --git a/pkg/frontend/admin_platformworkloadidentityroleset_put_test.go b/pkg/frontend/admin_platformworkloadidentityroleset_put_test.go new file mode 100644 index 00000000000..0c632dc93e8 --- /dev/null +++ b/pkg/frontend/admin_platformworkloadidentityroleset_put_test.go @@ -0,0 +1,788 @@ +package frontend + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "context" + "net/http" + "testing" + + "github.com/Azure/ARO-RP/pkg/api" + "github.com/Azure/ARO-RP/pkg/api/admin" + "github.com/Azure/ARO-RP/pkg/metrics/noop" + testdatabase "github.com/Azure/ARO-RP/test/database" +) + +func TestPlatformWorkloadIdentityRoleSetPut(t *testing.T) { + ctx := context.Background() + + type test struct { + name string + fixture func(f *testdatabase.Fixture) + body *admin.PlatformWorkloadIdentityRoleSet + wantStatusCode int + wantResponse *admin.PlatformWorkloadIdentityRoleSet + wantError string + wantDocuments []*api.PlatformWorkloadIdentityRoleSetDocument + } + + for _, tt := range []*test{ + { + name: "PUT to update an existing entry updates it in-place and results in StatusOK", + fixture: func(f *testdatabase.Fixture) { + f.AddPlatformWorkloadIdentityRoleSetDocuments( + &api.PlatformWorkloadIdentityRoleSetDocument{ + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + ) + }, + body: &admin.PlatformWorkloadIdentityRoleSet{ + Properties: admin.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []admin.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + }, + }, + }, + wantStatusCode: http.StatusOK, + wantResponse: &admin.PlatformWorkloadIdentityRoleSet{ + Properties: admin.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []admin.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + }, + }, + }, + wantDocuments: []*api.PlatformWorkloadIdentityRoleSetDocument{ + { + ID: "08080808-0808-0808-0808-080808080001", + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "PUT to add a new entry creates it successfully and results in StatusOK", + fixture: func(f *testdatabase.Fixture) { + f.AddPlatformWorkloadIdentityRoleSetDocuments( + &api.PlatformWorkloadIdentityRoleSetDocument{ + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + ) + }, + body: &admin.PlatformWorkloadIdentityRoleSet{ + Properties: admin.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.15", + PlatformWorkloadIdentityRoles: []admin.PlatformWorkloadIdentityRole{ + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + wantStatusCode: http.StatusCreated, + wantResponse: &admin.PlatformWorkloadIdentityRoleSet{ + Properties: admin.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.15", + PlatformWorkloadIdentityRoles: []admin.PlatformWorkloadIdentityRole{ + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + wantDocuments: []*api.PlatformWorkloadIdentityRoleSetDocument{ + { + ID: "08080808-0808-0808-0808-080808080001", + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + { + ID: "08080808-0808-0808-0808-080808080002", + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.15", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "PUT with missing request body results in StatusBadRequest", + fixture: func(f *testdatabase.Fixture) {}, + body: &admin.PlatformWorkloadIdentityRoleSet{}, + wantStatusCode: http.StatusBadRequest, + wantError: "400: InvalidParameter: properties.openShiftVersion: Must be provided", + wantDocuments: []*api.PlatformWorkloadIdentityRoleSetDocument{}, + }, + { + name: "PUT with missing OpenShiftVersion results in StatusBadRequest", + fixture: func(f *testdatabase.Fixture) { + f.AddPlatformWorkloadIdentityRoleSetDocuments( + &api.PlatformWorkloadIdentityRoleSetDocument{ + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + ) + }, + body: &admin.PlatformWorkloadIdentityRoleSet{ + Properties: admin.PlatformWorkloadIdentityRoleSetProperties{ + PlatformWorkloadIdentityRoles: []admin.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + }, + }, + }, + wantStatusCode: http.StatusBadRequest, + wantError: "400: InvalidParameter: properties.openShiftVersion: Must be provided", + wantDocuments: []*api.PlatformWorkloadIdentityRoleSetDocument{ + { + ID: "08080808-0808-0808-0808-080808080001", + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "PUT with missing PlatformWorkloadIdentityRoles results in StatusBadRequest", + fixture: func(f *testdatabase.Fixture) { + f.AddPlatformWorkloadIdentityRoleSetDocuments( + &api.PlatformWorkloadIdentityRoleSetDocument{ + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + ) + }, + body: &admin.PlatformWorkloadIdentityRoleSet{ + Properties: admin.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + }, + }, + wantStatusCode: http.StatusBadRequest, + wantError: "400: InvalidParameter: properties.platformWorkloadIdentityRoles: Must be provided and must be non-empty", + wantDocuments: []*api.PlatformWorkloadIdentityRoleSetDocument{ + { + ID: "08080808-0808-0808-0808-080808080001", + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "PUT with missing PlatformWorkloadIdentityRole.OperatorName results in StatusBadRequest", + fixture: func(f *testdatabase.Fixture) { + f.AddPlatformWorkloadIdentityRoleSetDocuments( + &api.PlatformWorkloadIdentityRoleSetDocument{ + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + ) + }, + body: &admin.PlatformWorkloadIdentityRoleSet{ + Properties: admin.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []admin.PlatformWorkloadIdentityRole{ + { + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + wantStatusCode: http.StatusBadRequest, + wantError: "400: InvalidParameter: properties.platformWorkloadIdentityRoles[0].operatorName: Must be provided", + wantDocuments: []*api.PlatformWorkloadIdentityRoleSetDocument{ + { + ID: "08080808-0808-0808-0808-080808080001", + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "PUT with missing PlatformWorkloadIdentityRole.RoleDefinitionName results in StatusBadRequest", + fixture: func(f *testdatabase.Fixture) { + f.AddPlatformWorkloadIdentityRoleSetDocuments( + &api.PlatformWorkloadIdentityRoleSetDocument{ + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + ) + }, + body: &admin.PlatformWorkloadIdentityRoleSet{ + Properties: admin.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []admin.PlatformWorkloadIdentityRole{ + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + wantStatusCode: http.StatusBadRequest, + wantError: "400: InvalidParameter: properties.platformWorkloadIdentityRoles[0].roleDefinitionName: Must be provided", + wantDocuments: []*api.PlatformWorkloadIdentityRoleSetDocument{ + { + ID: "08080808-0808-0808-0808-080808080001", + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "PUT with missing PlatformWorkloadIdentityRole.RoleDefinitionID results in StatusBadRequest", + fixture: func(f *testdatabase.Fixture) { + f.AddPlatformWorkloadIdentityRoleSetDocuments( + &api.PlatformWorkloadIdentityRoleSetDocument{ + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + ) + }, + body: &admin.PlatformWorkloadIdentityRoleSet{ + Properties: admin.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []admin.PlatformWorkloadIdentityRole{ + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + wantStatusCode: http.StatusBadRequest, + wantError: "400: InvalidParameter: properties.platformWorkloadIdentityRoles[0].roleDefinitionId: Must be provided", + wantDocuments: []*api.PlatformWorkloadIdentityRoleSetDocument{ + { + ID: "08080808-0808-0808-0808-080808080001", + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "PUT with missing PlatformWorkloadIdentityRole.ServiceAccounts results in StatusBadRequest", + fixture: func(f *testdatabase.Fixture) { + f.AddPlatformWorkloadIdentityRoleSetDocuments( + &api.PlatformWorkloadIdentityRoleSetDocument{ + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + ) + }, + body: &admin.PlatformWorkloadIdentityRoleSet{ + Properties: admin.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []admin.PlatformWorkloadIdentityRole{ + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + }, + }, + }, + }, + wantStatusCode: http.StatusBadRequest, + wantError: "400: InvalidParameter: properties.platformWorkloadIdentityRoles[0].serviceAccounts: Must be provided", + wantDocuments: []*api.PlatformWorkloadIdentityRoleSetDocument{ + { + ID: "08080808-0808-0808-0808-080808080001", + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "PUT with missing PlatformWorkloadIdentityRole.RoleDefinitionId and PlatformWorkloadIdentityRole.ServiceAccounts results in StatusBadRequest - tests the case where multiple attributes are missing and error message consists of messages about multiple missing properties joined together", + fixture: func(f *testdatabase.Fixture) { + f.AddPlatformWorkloadIdentityRoleSetDocuments( + &api.PlatformWorkloadIdentityRoleSetDocument{ + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + ) + }, + body: &admin.PlatformWorkloadIdentityRoleSet{ + Properties: admin.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []admin.PlatformWorkloadIdentityRole{ + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + }, + }, + }, + }, + wantStatusCode: http.StatusBadRequest, + wantError: "400: InvalidParameter: properties.platformWorkloadIdentityRoles[0].roleDefinitionId, properties.platformWorkloadIdentityRoles[0].serviceAccounts: Must be provided", + wantDocuments: []*api.PlatformWorkloadIdentityRoleSetDocument{ + { + ID: "08080808-0808-0808-0808-080808080001", + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + ti := newTestInfra(t).WithPlatformWorkloadIdentityRoleSets() + + defer ti.done() + + err := ti.buildFixtures(tt.fixture) + if err != nil { + t.Fatal(err) + } + + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, nil, ti.platformWorkloadIdentityRoleSetsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + if err != nil { + t.Fatal(err) + } + + go f.Run(ctx, nil, nil) + + resp, b, err := ti.request(http.MethodPut, "https://server/admin/platformworkloadidentityrolesets", + http.Header{ + "Content-Type": []string{"application/json"}, + }, tt.body) + if err != nil { + t.Fatal(err) + } + + err = validateResponse(resp, b, tt.wantStatusCode, tt.wantError, tt.wantResponse) + if err != nil { + t.Error(err) + } + + if tt.wantDocuments != nil { + ti.checker.AddPlatformWorkloadIdentityRoleSetDocuments(tt.wantDocuments...) + for _, err := range ti.checker.CheckPlatformWorkloadIdentityRoleSets(ti.platformWorkloadIdentityRoleSetsClient) { + t.Error(err) + } + } + }) + } +} diff --git a/pkg/frontend/asyncoperationresult_get_test.go b/pkg/frontend/asyncoperationresult_get_test.go index 516fcac902a..a48eefad21b 100644 --- a/pkg/frontend/asyncoperationresult_get_test.go +++ b/pkg/frontend/asyncoperationresult_get_test.go @@ -136,7 +136,7 @@ func TestGetAsyncOperationResult(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/asyncoperationsstatus_get_test.go b/pkg/frontend/asyncoperationsstatus_get_test.go index f6a6d3a9a1f..9533d02db1b 100644 --- a/pkg/frontend/asyncoperationsstatus_get_test.go +++ b/pkg/frontend/asyncoperationsstatus_get_test.go @@ -183,7 +183,7 @@ func TestGetAsyncOperationsStatus(t *testing.T) { ti.asyncOperationsClient.SetError(tt.dbError) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/changefeed.go b/pkg/frontend/changefeed.go index 4291e1c9aa0..5bb5c32017e 100644 --- a/pkg/frontend/changefeed.go +++ b/pkg/frontend/changefeed.go @@ -12,7 +12,7 @@ import ( "github.com/Azure/ARO-RP/pkg/util/recover" ) -func (f *frontend) changefeed(ctx context.Context) { +func (f *frontend) changefeedOcpVersions(ctx context.Context) { defer recover.Panic(f.baseLog) // f.dbOpenShiftVersions will be nil when running unit tests. Return here to avoid nil pointer panic @@ -20,15 +20,30 @@ func (f *frontend) changefeed(ctx context.Context) { return } - frontendIterator := f.dbOpenShiftVersions.ChangeFeed() + ocpVersionsIterator := f.dbOpenShiftVersions.ChangeFeed() t := time.NewTicker(10 * time.Second) defer t.Stop() - f.updateFromIterator(ctx, t, frontendIterator) + f.updateFromIteratorOcpVersions(ctx, t, ocpVersionsIterator) } -func (f *frontend) updateFromIterator(ctx context.Context, ticker *time.Ticker, frontendIterator cosmosdb.OpenShiftVersionDocumentIterator) { +func (f *frontend) changefeedRoleSets(ctx context.Context) { + defer recover.Panic(f.baseLog) + + if f.dbPlatformWorkloadIdentityRoleSets == nil { + return + } + + roleSetsIterator := f.dbPlatformWorkloadIdentityRoleSets.ChangeFeed() + + t := time.NewTicker(10 * time.Second) + defer t.Stop() + + f.updateFromIteratorRoleSets(ctx, t, roleSetsIterator) +} + +func (f *frontend) updateFromIteratorOcpVersions(ctx context.Context, ticker *time.Ticker, frontendIterator cosmosdb.OpenShiftVersionDocumentIterator) { for { successful := true @@ -47,7 +62,7 @@ func (f *frontend) updateFromIterator(ctx context.Context, ticker *time.Ticker, } if successful { - f.lastChangefeed.Store(time.Now()) + f.lastOcpVersionsChangefeed.Store(time.Now()) } select { @@ -60,8 +75,8 @@ func (f *frontend) updateFromIterator(ctx context.Context, ticker *time.Ticker, // updateOcpVersions adds enabled versions to the frontend cache func (f *frontend) updateOcpVersions(docs []*api.OpenShiftVersionDocument) { - f.mu.Lock() - defer f.mu.Unlock() + f.ocpVersionsMu.Lock() + defer f.ocpVersionsMu.Unlock() for _, doc := range docs { if doc.OpenShiftVersion.Deleting || !doc.OpenShiftVersion.Properties.Enabled { @@ -75,3 +90,47 @@ func (f *frontend) updateOcpVersions(docs []*api.OpenShiftVersionDocument) { } } } + +func (f *frontend) updateFromIteratorRoleSets(ctx context.Context, ticker *time.Ticker, frontendIterator cosmosdb.PlatformWorkloadIdentityRoleSetDocumentIterator) { + for { + successful := true + + for { + docs, err := frontendIterator.Next(ctx, -1) + if err != nil { + successful = false + f.baseLog.Error(err) + break + } + if docs == nil { + break + } + + f.updatePlatformWorkloadIdentityRoleSets(docs.PlatformWorkloadIdentityRoleSetDocuments) + } + + if successful { + f.lastPlatformWorkloadIdentityRoleSetsChangefeed.Store(time.Now()) + } + + select { + case <-ticker.C: + case <-ctx.Done(): + return + } + } +} + +func (f *frontend) updatePlatformWorkloadIdentityRoleSets(docs []*api.PlatformWorkloadIdentityRoleSetDocument) { + f.platformWorkloadIdentityRoleSetsMu.Lock() + defer f.platformWorkloadIdentityRoleSetsMu.Unlock() + + for _, doc := range docs { + if doc.PlatformWorkloadIdentityRoleSet.Deleting { + // https://docs.microsoft.com/en-us/azure/cosmos-db/change-feed-design-patterns#deletes + delete(f.availablePlatformWorkloadIdentityRoleSets, doc.PlatformWorkloadIdentityRoleSet.Properties.OpenShiftVersion) + } else { + f.availablePlatformWorkloadIdentityRoleSets[doc.PlatformWorkloadIdentityRoleSet.Properties.OpenShiftVersion] = doc.PlatformWorkloadIdentityRoleSet + } + } +} diff --git a/pkg/frontend/changefeed_test.go b/pkg/frontend/changefeed_test.go index fb56d28c464..6e572d8cd86 100644 --- a/pkg/frontend/changefeed_test.go +++ b/pkg/frontend/changefeed_test.go @@ -14,7 +14,7 @@ import ( "github.com/Azure/ARO-RP/pkg/util/cmp" ) -func TestUpdateFromIterator(t *testing.T) { +func TestUpdateFromIteratorOcpVersions(t *testing.T) { for _, tt := range []struct { name string docsInIterator []*api.OpenShiftVersionDocument @@ -22,7 +22,7 @@ func TestUpdateFromIterator(t *testing.T) { wantVersions map[string]*api.OpenShiftVersion }{ { - name: "add to empty", + name: "Add a new doc from the changefeed to an empty frontend cache", docsInIterator: []*api.OpenShiftVersionDocument{ { OpenShiftVersion: &api.OpenShiftVersion{ @@ -44,7 +44,7 @@ func TestUpdateFromIterator(t *testing.T) { }, }, { - name: "do nothing", + name: "Docs in changefeed match docs in frontend cache - no changes needed", docsInIterator: []*api.OpenShiftVersionDocument{ { OpenShiftVersion: &api.OpenShiftVersion{ @@ -73,7 +73,7 @@ func TestUpdateFromIterator(t *testing.T) { }, }, { - name: "add to not empty", + name: "Add a new doc from the iterator to a non-empty frontend cache", docsInIterator: []*api.OpenShiftVersionDocument{ { OpenShiftVersion: &api.OpenShiftVersion{ @@ -108,7 +108,7 @@ func TestUpdateFromIterator(t *testing.T) { }, }, { - name: "remove existing", + name: "A doc present in the frontend cache is marked deleting in the changefeed - remove it from the cache", docsInIterator: []*api.OpenShiftVersionDocument{ { OpenShiftVersion: &api.OpenShiftVersion{ @@ -152,7 +152,7 @@ func TestUpdateFromIterator(t *testing.T) { }, }, { - name: "remove disabled versions", + name: "A doc present in the frontend cache is marked disabled in the changefeed - remove it from the cache", docsInIterator: []*api.OpenShiftVersionDocument{ { OpenShiftVersion: &api.OpenShiftVersion{ @@ -184,7 +184,7 @@ func TestUpdateFromIterator(t *testing.T) { fakeIterator := cosmosdb.NewFakeOpenShiftVersionDocumentIterator(tt.docsInIterator, 0) - go frontend.updateFromIterator(ctx, ticker, fakeIterator) + go frontend.updateFromIteratorOcpVersions(ctx, ticker, fakeIterator) time.Sleep(time.Second) cancel() @@ -194,3 +194,345 @@ func TestUpdateFromIterator(t *testing.T) { }) } } + +func TestUpdateFromIteratorRoleSets(t *testing.T) { + for _, tt := range []struct { + name string + docsInIterator []*api.PlatformWorkloadIdentityRoleSetDocument + roleSets map[string]*api.PlatformWorkloadIdentityRoleSet + wantRoleSets map[string]*api.PlatformWorkloadIdentityRoleSet + }{ + { + name: "add to empty", + docsInIterator: []*api.PlatformWorkloadIdentityRoleSetDocument{ + { + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + }, + }, + }, + }, + }, + roleSets: map[string]*api.PlatformWorkloadIdentityRoleSet{}, + wantRoleSets: map[string]*api.PlatformWorkloadIdentityRoleSet{ + "4.14": { + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + }, + }, + }, + }, + }, + { + name: "do nothing", + docsInIterator: []*api.PlatformWorkloadIdentityRoleSetDocument{ + { + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + }, + }, + }, + }, + }, + roleSets: map[string]*api.PlatformWorkloadIdentityRoleSet{ + "4.14": { + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + }, + }, + }, + }, + wantRoleSets: map[string]*api.PlatformWorkloadIdentityRoleSet{ + "4.14": { + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + }, + }, + }, + }, + }, + { + name: "add to not empty", + docsInIterator: []*api.PlatformWorkloadIdentityRoleSetDocument{ + { + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + }, + }, + }, + }, + { + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.15", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + }, + roleSets: map[string]*api.PlatformWorkloadIdentityRoleSet{ + "4.14": { + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + }, + }, + }, + }, + wantRoleSets: map[string]*api.PlatformWorkloadIdentityRoleSet{ + "4.14": { + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + }, + }, + }, + "4.15": { + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.15", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + }, + { + name: "remove existing", + docsInIterator: []*api.PlatformWorkloadIdentityRoleSetDocument{ + { + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + }, + }, + Deleting: true, + }, + }, + { + PlatformWorkloadIdentityRoleSet: &api.PlatformWorkloadIdentityRoleSet{ + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.15", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + }, + roleSets: map[string]*api.PlatformWorkloadIdentityRoleSet{ + "4.14": { + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + }, + }, + }, + "4.15": { + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.15", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + wantRoleSets: map[string]*api.PlatformWorkloadIdentityRoleSet{ + "4.15": { + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.15", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + }, + } { + t.Run(tt.name, func(t *testing.T) { + ticker := time.NewTicker(1) + ctx, cancel := context.WithCancel(context.TODO()) + + frontend := frontend{ + availablePlatformWorkloadIdentityRoleSets: tt.roleSets, + } + + fakeIterator := cosmosdb.NewFakePlatformWorkloadIdentityRoleSetDocumentIterator(tt.docsInIterator, 0) + + go frontend.updateFromIteratorRoleSets(ctx, ticker, fakeIterator) + time.Sleep(time.Second) + cancel() + + if !reflect.DeepEqual(frontend.availablePlatformWorkloadIdentityRoleSets, tt.wantRoleSets) { + t.Error(cmp.Diff(frontend.availablePlatformWorkloadIdentityRoleSets, tt.wantRoleSets)) + } + }) + } +} diff --git a/pkg/frontend/clustermanager_delete_test.go b/pkg/frontend/clustermanager_delete_test.go index 7c85ef3e453..91d83e6d8a2 100644 --- a/pkg/frontend/clustermanager_delete_test.go +++ b/pkg/frontend/clustermanager_delete_test.go @@ -120,7 +120,7 @@ func TestDeleteClusterManagerConfiguration(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, ti.clusterManagerDatabase, nil, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, ti.clusterManagerDatabase, nil, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/clustermanager_get_test.go b/pkg/frontend/clustermanager_get_test.go index eaf73d78d01..0f38667ab22 100644 --- a/pkg/frontend/clustermanager_get_test.go +++ b/pkg/frontend/clustermanager_get_test.go @@ -123,7 +123,7 @@ func TestGetClusterManagerConfiguration(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, ti.clusterManagerDatabase, nil, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, ti.clusterManagerDatabase, nil, nil, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/clustermanager_putorpatch_test.go b/pkg/frontend/clustermanager_putorpatch_test.go index 7708c7e459e..9bd73948a57 100644 --- a/pkg/frontend/clustermanager_putorpatch_test.go +++ b/pkg/frontend/clustermanager_putorpatch_test.go @@ -201,7 +201,7 @@ func TestPutOrPatchClusterManagerConfiguration(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/fixetcd_test.go b/pkg/frontend/fixetcd_test.go index b25b9a482b1..43ad982bbb4 100644 --- a/pkg/frontend/fixetcd_test.go +++ b/pkg/frontend/fixetcd_test.go @@ -523,6 +523,7 @@ func TestFixEtcd(t *testing.T) { ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, + nil, api.APIs, &noop.Noop{}, &noop.Noop{}, diff --git a/pkg/frontend/frontend.go b/pkg/frontend/frontend.go index 5d300085111..9e62d3000cb 100644 --- a/pkg/frontend/frontend.go +++ b/pkg/frontend/frontend.go @@ -57,18 +57,22 @@ type frontend struct { apiVersionMiddleware middleware.ApiVersionValidator maintenanceMiddleware middleware.MaintenanceMiddleware - dbAsyncOperations database.AsyncOperations - dbClusterManagerConfiguration database.ClusterManagerConfigurations - dbOpenShiftClusters database.OpenShiftClusters - dbSubscriptions database.Subscriptions - dbOpenShiftVersions database.OpenShiftVersions - - defaultOcpVersion string // always enabled - enabledOcpVersions map[string]*api.OpenShiftVersion - apis map[string]*api.Version - - lastChangefeed atomic.Value //time.Time - mu sync.RWMutex + dbAsyncOperations database.AsyncOperations + dbClusterManagerConfiguration database.ClusterManagerConfigurations + dbOpenShiftClusters database.OpenShiftClusters + dbSubscriptions database.Subscriptions + dbOpenShiftVersions database.OpenShiftVersions + dbPlatformWorkloadIdentityRoleSets database.PlatformWorkloadIdentityRoleSets + + defaultOcpVersion string // always enabled + enabledOcpVersions map[string]*api.OpenShiftVersion + availablePlatformWorkloadIdentityRoleSets map[string]*api.PlatformWorkloadIdentityRoleSet + apis map[string]*api.Version + + lastOcpVersionsChangefeed atomic.Value //time.Time + lastPlatformWorkloadIdentityRoleSetsChangefeed atomic.Value + ocpVersionsMu sync.RWMutex + platformWorkloadIdentityRoleSetsMu sync.RWMutex aead encryption.AEAD @@ -107,6 +111,7 @@ type Runnable interface { Run(context.Context, <-chan struct{}, chan<- struct{}) } +// TODO: Get the number of function parameters under control :D // NewFrontend returns a new runnable frontend func NewFrontend(ctx context.Context, auditLog *logrus.Entry, @@ -117,6 +122,7 @@ func NewFrontend(ctx context.Context, dbOpenShiftClusters database.OpenShiftClusters, dbSubscriptions database.Subscriptions, dbOpenShiftVersions database.OpenShiftVersions, + dbPlatformWorkloadIdentityRoleSets database.PlatformWorkloadIdentityRoleSets, apis map[string]*api.Version, m metrics.Emitter, clusterm metrics.Emitter, @@ -148,18 +154,19 @@ func NewFrontend(ctx context.Context, AdminAuth: _env.AdminClientAuthorizer(), ArmAuth: _env.ArmClientAuthorizer(), }, - dbAsyncOperations: dbAsyncOperations, - dbClusterManagerConfiguration: dbClusterManagerConfiguration, - dbOpenShiftClusters: dbOpenShiftClusters, - dbSubscriptions: dbSubscriptions, - dbOpenShiftVersions: dbOpenShiftVersions, - apis: apis, - m: middleware.MetricsMiddleware{Emitter: m}, - maintenanceMiddleware: middleware.MaintenanceMiddleware{Emitter: clusterm}, - aead: aead, - hiveClusterManager: hiveClusterManager, - kubeActionsFactory: kubeActionsFactory, - azureActionsFactory: azureActionsFactory, + dbAsyncOperations: dbAsyncOperations, + dbClusterManagerConfiguration: dbClusterManagerConfiguration, + dbOpenShiftClusters: dbOpenShiftClusters, + dbSubscriptions: dbSubscriptions, + dbOpenShiftVersions: dbOpenShiftVersions, + dbPlatformWorkloadIdentityRoleSets: dbPlatformWorkloadIdentityRoleSets, + apis: apis, + m: middleware.MetricsMiddleware{Emitter: m}, + maintenanceMiddleware: middleware.MaintenanceMiddleware{Emitter: clusterm}, + aead: aead, + hiveClusterManager: hiveClusterManager, + kubeActionsFactory: kubeActionsFactory, + azureActionsFactory: azureActionsFactory, quotaValidator: quotaValidator{}, skuValidator: skuValidator{}, @@ -167,7 +174,8 @@ func NewFrontend(ctx context.Context, clusterEnricher: enricher, - enabledOcpVersions: map[string]*api.OpenShiftVersion{}, + enabledOcpVersions: map[string]*api.OpenShiftVersion{}, + availablePlatformWorkloadIdentityRoleSets: map[string]*api.PlatformWorkloadIdentityRoleSet{}, bucketAllocator: &bucket.Random{}, @@ -277,6 +285,8 @@ func (f *frontend) chiAuthenticatedRoutes(router chi.Router) { r.Get("/operationresults/{operationId}", f.getAsyncOperationResult) r.Get("/openshiftversions", f.listInstallVersions) + + r.Get("/platformworkloadidentityrolesets", f.listPlatformWorkloadIdentityRoleSets) }) }) }) @@ -288,6 +298,10 @@ func (f *frontend) chiAuthenticatedRoutes(router chi.Router) { r.Get("/", f.getAdminOpenShiftVersions) r.Put("/", f.putAdminOpenShiftVersion) }) + r.Route("/platformworkloadidentityrolesets", func(r chi.Router) { + r.Get("/", f.getAdminPlatformWorkloadIdentityRoleSets) + r.Put("/", f.putAdminPlatformWorkloadIdentityRoleSet) + }) r.Get("/supportedvmsizes", f.supportedvmsizes) r.Route("/subscriptions/{subscriptionId}", func(r chi.Router) { @@ -372,7 +386,8 @@ func (f *frontend) setupRouter() chi.Router { func (f *frontend) Run(ctx context.Context, stop <-chan struct{}, done chan<- struct{}) { defer recover.Panic(f.baseLog) - go f.changefeed(ctx) + go f.changefeedOcpVersions(ctx) + go f.changefeedRoleSets(ctx) if stop != nil { go func() { diff --git a/pkg/frontend/openshiftcluster_applensdetectors_test.go b/pkg/frontend/openshiftcluster_applensdetectors_test.go index fc3bc445548..b1665012e32 100644 --- a/pkg/frontend/openshiftcluster_applensdetectors_test.go +++ b/pkg/frontend/openshiftcluster_applensdetectors_test.go @@ -97,7 +97,7 @@ func TestAppLensDetectors(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, func(*logrus.Entry, env.Interface, *api.OpenShiftCluster, *api.SubscriptionDocument) (adminactions.AzureActions, error) { return a, nil }, nil) diff --git a/pkg/frontend/openshiftcluster_delete_test.go b/pkg/frontend/openshiftcluster_delete_test.go index e9774b58b76..08883437ff1 100644 --- a/pkg/frontend/openshiftcluster_delete_test.go +++ b/pkg/frontend/openshiftcluster_delete_test.go @@ -114,7 +114,7 @@ func TestDeleteOpenShiftCluster(t *testing.T) { ti.subscriptionsClient.SetError(tt.dbError) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/openshiftcluster_get_test.go b/pkg/frontend/openshiftcluster_get_test.go index 65fff16875c..1ac87c137e5 100644 --- a/pkg/frontend/openshiftcluster_get_test.go +++ b/pkg/frontend/openshiftcluster_get_test.go @@ -95,7 +95,7 @@ func TestGetOpenShiftCluster(t *testing.T) { ti.openShiftClustersClient.SetError(tt.dbError) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, ti.enricher) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, ti.enricher) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/openshiftcluster_list_test.go b/pkg/frontend/openshiftcluster_list_test.go index c22c16c99f7..9fc49f4bda7 100644 --- a/pkg/frontend/openshiftcluster_list_test.go +++ b/pkg/frontend/openshiftcluster_list_test.go @@ -204,7 +204,7 @@ func TestListOpenShiftCluster(t *testing.T) { aead := testdatabase.NewFakeAEAD() - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, aead, nil, nil, nil, ti.enricher) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, aead, nil, nil, nil, ti.enricher) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/openshiftcluster_preflightvalidation_test.go b/pkg/frontend/openshiftcluster_preflightvalidation_test.go index fe70615c31c..1312de21a6e 100644 --- a/pkg/frontend/openshiftcluster_preflightvalidation_test.go +++ b/pkg/frontend/openshiftcluster_preflightvalidation_test.go @@ -158,14 +158,14 @@ func TestPreflightValidation(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, ti.openShiftVersionsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, ti.openShiftVersionsDatabase, ti.platformWorkloadIdentityRoleSetsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } oc := tt.preflightRequest() go f.Run(ctx, nil, nil) - f.mu.Lock() + f.ocpVersionsMu.Lock() f.defaultOcpVersion = "4.10.0" f.enabledOcpVersions = map[string]*api.OpenShiftVersion{ f.defaultOcpVersion: { @@ -174,7 +174,7 @@ func TestPreflightValidation(t *testing.T) { }, }, } - f.mu.Unlock() + f.ocpVersionsMu.Unlock() headers := http.Header{ "Content-Type": []string{"application/json"}, diff --git a/pkg/frontend/openshiftcluster_putorpatch_test.go b/pkg/frontend/openshiftcluster_putorpatch_test.go index 540616ccf9d..15ee5181237 100644 --- a/pkg/frontend/openshiftcluster_putorpatch_test.go +++ b/pkg/frontend/openshiftcluster_putorpatch_test.go @@ -1727,7 +1727,7 @@ func TestPutOrPatchOpenShiftClusterAdminAPI(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, apis, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, ti.enricher) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, apis, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, ti.enricher) if err != nil { t.Fatal(err) } @@ -2800,7 +2800,7 @@ func TestPutOrPatchOpenShiftCluster(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, ti.openShiftVersionsDatabase, apis, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, ti.enricher) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, ti.openShiftVersionsDatabase, ti.platformWorkloadIdentityRoleSetsDatabase, apis, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, ti.enricher) if err != nil { t.Fatal(err) } @@ -2817,14 +2817,14 @@ func TestPutOrPatchOpenShiftCluster(t *testing.T) { } go f.Run(ctx, nil, nil) - f.mu.Lock() + f.ocpVersionsMu.Lock() f.enabledOcpVersions = tt.changeFeed for key, doc := range tt.changeFeed { if doc.Properties.Default { f.defaultOcpVersion = key } } - f.mu.Unlock() + f.ocpVersionsMu.Unlock() oc := &v20200430.OpenShiftCluster{} if tt.request != nil { @@ -3133,7 +3133,7 @@ func TestPutOrPatchOpenShiftClusterValidated(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, ti.openShiftVersionsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, ti.enricher) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, ti.openShiftVersionsDatabase, ti.platformWorkloadIdentityRoleSetsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, ti.enricher) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/openshiftclustercredentials_post_test.go b/pkg/frontend/openshiftclustercredentials_post_test.go index 54f445928fd..14400926f3f 100644 --- a/pkg/frontend/openshiftclustercredentials_post_test.go +++ b/pkg/frontend/openshiftclustercredentials_post_test.go @@ -267,7 +267,7 @@ func TestPostOpenShiftClusterCredentials(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, apis, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, apis, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/openshiftclusterkubeconfigcredentials_post_test.go b/pkg/frontend/openshiftclusterkubeconfigcredentials_post_test.go index c233024ba3e..f8461d56679 100644 --- a/pkg/frontend/openshiftclusterkubeconfigcredentials_post_test.go +++ b/pkg/frontend/openshiftclusterkubeconfigcredentials_post_test.go @@ -242,7 +242,7 @@ func TestPostOpenShiftClusterKubeConfigCredentials(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, apis, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, apis, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/openshiftversions_list.go b/pkg/frontend/openshiftversions_list.go index c3de112b302..9306dc628fd 100644 --- a/pkg/frontend/openshiftversions_list.go +++ b/pkg/frontend/openshiftversions_list.go @@ -35,11 +35,11 @@ func (f *frontend) listInstallVersions(w http.ResponseWriter, r *http.Request) { func (f *frontend) getEnabledInstallVersions(ctx context.Context) []*api.OpenShiftVersion { versions := make([]*api.OpenShiftVersion, 0) - f.mu.RLock() + f.ocpVersionsMu.RLock() for _, v := range f.enabledOcpVersions { versions = append(versions, v) } - f.mu.RUnlock() + f.ocpVersionsMu.RUnlock() return versions } diff --git a/pkg/frontend/openshiftversions_list_test.go b/pkg/frontend/openshiftversions_list_test.go index 3f1413c9003..11b90ed9db7 100644 --- a/pkg/frontend/openshiftversions_list_test.go +++ b/pkg/frontend/openshiftversions_list_test.go @@ -78,21 +78,21 @@ func TestListInstallVersions(t *testing.T) { ti := newTestInfra(t).WithSubscriptions().WithOpenShiftVersions() defer ti.done() - frontend, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, ti.openShiftVersionsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + frontend, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, ti.openShiftVersionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } go frontend.Run(ctx, nil, nil) - frontend.mu.Lock() + frontend.ocpVersionsMu.Lock() frontend.enabledOcpVersions = tt.changeFeed for key, doc := range tt.changeFeed { if doc.Properties.Enabled { frontend.defaultOcpVersion = key } } - frontend.mu.Unlock() + frontend.ocpVersionsMu.Unlock() resp, b, err := ti.request(method, fmt.Sprintf("https://server/subscriptions/%s/providers/Microsoft.RedHatOpenShift/locations/%s/openshiftversions?api-version=%s", mockSubID, ti.env.Location(), tt.apiVersion), diff --git a/pkg/frontend/platformworkloadidentityrolesets_list.go b/pkg/frontend/platformworkloadidentityrolesets_list.go new file mode 100644 index 00000000000..c03d7de4062 --- /dev/null +++ b/pkg/frontend/platformworkloadidentityrolesets_list.go @@ -0,0 +1,45 @@ +package frontend + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "context" + "encoding/json" + "net/http" + + "github.com/go-chi/chi/v5" + "github.com/sirupsen/logrus" + + "github.com/Azure/ARO-RP/pkg/api" + "github.com/Azure/ARO-RP/pkg/frontend/middleware" +) + +func (f *frontend) listPlatformWorkloadIdentityRoleSets(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + log := ctx.Value(middleware.ContextKeyLog).(*logrus.Entry) + apiVersion := r.URL.Query().Get(api.APIVersionKey) + resourceProviderNamespace := chi.URLParam(r, "resourceProviderNamespace") + if f.apis[apiVersion].PlatformWorkloadIdentityRoleSetConverter == nil { + api.WriteError(w, http.StatusBadRequest, api.CloudErrorCodeInvalidResourceType, "", "The endpoint could not be found in the namespace '%s' for api version '%s'.", resourceProviderNamespace, apiVersion) + return + } + + roleSets := f.getAvailablePlatformWorkloadIdentityRoleSets(ctx) + converter := f.apis[apiVersion].PlatformWorkloadIdentityRoleSetConverter + + b, err := json.MarshalIndent(converter.ToExternalList(roleSets), "", " ") + reply(log, w, nil, b, err) +} + +func (f *frontend) getAvailablePlatformWorkloadIdentityRoleSets(ctx context.Context) []*api.PlatformWorkloadIdentityRoleSet { + roleSets := make([]*api.PlatformWorkloadIdentityRoleSet, 0) + + f.platformWorkloadIdentityRoleSetsMu.RLock() + for _, pwirs := range f.availablePlatformWorkloadIdentityRoleSets { + roleSets = append(roleSets, pwirs) + } + f.platformWorkloadIdentityRoleSetsMu.RUnlock() + + return roleSets +} diff --git a/pkg/frontend/platformworkloadidentityrolesets_list_test.go b/pkg/frontend/platformworkloadidentityrolesets_list_test.go new file mode 100644 index 00000000000..1ab45d9317f --- /dev/null +++ b/pkg/frontend/platformworkloadidentityrolesets_list_test.go @@ -0,0 +1,176 @@ +package frontend + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "sort" + "testing" + + "github.com/Azure/ARO-RP/pkg/api" + "github.com/Azure/ARO-RP/pkg/api/v20240812preview" + "github.com/Azure/ARO-RP/pkg/metrics/noop" + "github.com/Azure/ARO-RP/pkg/util/version" +) + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +func TestListPlatformWorkloadIdentityRoleSets(t *testing.T) { + mockSubID := "00000000-0000-0000-0000-000000000000" + method := http.MethodGet + ctx := context.Background() + + for _, tt := range []struct { + name string + changeFeed map[string]*api.PlatformWorkloadIdentityRoleSet + apiVersion string + wantStatusCode int + wantResponse v20240812preview.PlatformWorkloadIdentityRoleSetList + wantError string + }{ + { + name: "GET request results in StatusOK", + changeFeed: map[string]*api.PlatformWorkloadIdentityRoleSet{ + "4.14": { + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + }, + }, + }, + "4.15": { + Properties: api.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.15", + PlatformWorkloadIdentityRoles: []api.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + ServiceAccounts: []string{ + "openshift-cloud-controller-manager:cloud-controller-manager", + }, + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + ServiceAccounts: []string{ + "openshift-ingress-operator:ingress-operator", + }, + }, + }, + }, + }, + }, + apiVersion: "2024-08-12-preview", + wantStatusCode: 200, + wantResponse: v20240812preview.PlatformWorkloadIdentityRoleSetList{ + PlatformWorkloadIdentityRoleSets: []*v20240812preview.PlatformWorkloadIdentityRoleSet{ + { + Properties: v20240812preview.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.14", + PlatformWorkloadIdentityRoles: []v20240812preview.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + }, + }, + }, + }, + { + Properties: v20240812preview.PlatformWorkloadIdentityRoleSetProperties{ + OpenShiftVersion: "4.15", + PlatformWorkloadIdentityRoles: []v20240812preview.PlatformWorkloadIdentityRole{ + { + OperatorName: "CloudControllerManager", + RoleDefinitionName: "Azure RedHat OpenShift Cloud Controller Manager Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/a1f96423-95ce-4224-ab27-4e3dc72facd4", + }, + { + OperatorName: "ClusterIngressOperator", + RoleDefinitionName: "Azure RedHat OpenShift Cluster Ingress Operator Role", + RoleDefinitionID: "/providers/Microsoft.Authorization/roleDefinitions/0336e1d3-7a87-462b-b6db-342b63f7802c", + }, + }, + }, + }, + }, + }, + }, + { + name: "GET request with non-existent API version results in StatusBadRequest", + apiVersion: "invalid", + wantStatusCode: http.StatusBadRequest, + wantError: "400: InvalidResourceType: : The resource type '' could not be found in the namespace 'microsoft.redhatopenshift' for api version 'invalid'.", + }, + { + name: "GET request with old API version that doesn't support MIWI results in StatusBadRequest", + apiVersion: "2022-09-04", + wantStatusCode: http.StatusBadRequest, + wantError: "400: InvalidResourceType: : The endpoint could not be found in the namespace 'microsoft.redhatopenshift' for api version '2022-09-04'.", + }, + } { + t.Run(tt.name, func(t *testing.T) { + ti := newTestInfra(t).WithSubscriptions().WithPlatformWorkloadIdentityRoleSets() + defer ti.done() + + frontend, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, nil, nil, nil, nil, nil, ti.platformWorkloadIdentityRoleSetsDatabase, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + if err != nil { + t.Fatal(err) + } + + go frontend.Run(ctx, nil, nil) + + frontend.platformWorkloadIdentityRoleSetsMu.Lock() + frontend.availablePlatformWorkloadIdentityRoleSets = tt.changeFeed + frontend.platformWorkloadIdentityRoleSetsMu.Unlock() + + resp, b, err := ti.request(method, + fmt.Sprintf("https://server/subscriptions/%s/providers/Microsoft.RedHatOpenShift/locations/%s/platformworkloadidentityrolesets?api-version=%s", mockSubID, ti.env.Location(), tt.apiVersion), + nil, nil) + if err != nil { + t.Fatal(err) + } + + // sort the response as the version order might be changed + if b != nil && resp.StatusCode == http.StatusOK { + var r v20240812preview.PlatformWorkloadIdentityRoleSetList + if err = json.Unmarshal(b, &r); err != nil { + t.Error(err) + } + + sort.Slice(r.PlatformWorkloadIdentityRoleSets, func(i, j int) bool { + return version.CreateSemverFromMinorVersionString(r.PlatformWorkloadIdentityRoleSets[i].Properties.OpenShiftVersion).LessThan(*version.CreateSemverFromMinorVersionString(r.PlatformWorkloadIdentityRoleSets[j].Properties.OpenShiftVersion)) + }) + + b, err = json.Marshal(r) + if err != nil { + t.Error(err) + } + } + + // marshal the expected response into a []byte otherwise + // it will compare zero values to omitempty json tags + want, err := json.Marshal(tt.wantResponse) + if err != nil { + t.Error(err) + } + + err = validateResponse(resp, b, tt.wantStatusCode, tt.wantError, want) + if err != nil { + t.Error(err) + } + }) + } +} diff --git a/pkg/frontend/ready_get.go b/pkg/frontend/ready_get.go index 965c898161b..b62a6a7ff79 100644 --- a/pkg/frontend/ready_get.go +++ b/pkg/frontend/ready_get.go @@ -21,9 +21,10 @@ func (f *frontend) checkReady() bool { return false } - _, ok := f.lastChangefeed.Load().(time.Time) + _, okOcpVersions := f.lastOcpVersionsChangefeed.Load().(time.Time) + _, okPlatformWorkloadIdentityRoleSets := f.lastPlatformWorkloadIdentityRoleSetsChangefeed.Load().(time.Time) - return ok && + return okOcpVersions && okPlatformWorkloadIdentityRoleSets && f.ready.Load().(bool) && f.env.ArmClientAuthorizer().IsReady() && f.env.AdminClientAuthorizer().IsReady() diff --git a/pkg/frontend/security_test.go b/pkg/frontend/security_test.go index ac47d43e28d..1f501074234 100644 --- a/pkg/frontend/security_test.go +++ b/pkg/frontend/security_test.go @@ -77,14 +77,15 @@ func TestSecurity(t *testing.T) { log := logrus.NewEntry(logrus.StandardLogger()) auditHook, auditEntry := testlog.NewAudit() - f, err := NewFrontend(ctx, auditEntry, log, _env, nil, nil, nil, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, auditEntry, log, _env, nil, nil, nil, nil, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } // enable /healthz to return 200 f.startTime = time.Time{} - f.lastChangefeed.Store(time.Time{}) + f.lastOcpVersionsChangefeed.Store(time.Time{}) + f.lastPlatformWorkloadIdentityRoleSetsChangefeed.Store(time.Time{}) go f.Run(ctx, nil, nil) diff --git a/pkg/frontend/shared_test.go b/pkg/frontend/shared_test.go index bb37ab22929..c7ffb6523ba 100644 --- a/pkg/frontend/shared_test.go +++ b/pkg/frontend/shared_test.go @@ -68,18 +68,20 @@ type testInfra struct { fixture *testdatabase.Fixture checker *testdatabase.Checker - openShiftClustersClient *cosmosdb.FakeOpenShiftClusterDocumentClient - openShiftClustersDatabase database.OpenShiftClusters - asyncOperationsClient *cosmosdb.FakeAsyncOperationDocumentClient - asyncOperationsDatabase database.AsyncOperations - billingClient *cosmosdb.FakeBillingDocumentClient - billingDatabase database.Billing - clusterManagerClient *cosmosdb.FakeClusterManagerConfigurationDocumentClient - clusterManagerDatabase database.ClusterManagerConfigurations - subscriptionsClient *cosmosdb.FakeSubscriptionDocumentClient - subscriptionsDatabase database.Subscriptions - openShiftVersionsClient *cosmosdb.FakeOpenShiftVersionDocumentClient - openShiftVersionsDatabase database.OpenShiftVersions + openShiftClustersClient *cosmosdb.FakeOpenShiftClusterDocumentClient + openShiftClustersDatabase database.OpenShiftClusters + asyncOperationsClient *cosmosdb.FakeAsyncOperationDocumentClient + asyncOperationsDatabase database.AsyncOperations + billingClient *cosmosdb.FakeBillingDocumentClient + billingDatabase database.Billing + clusterManagerClient *cosmosdb.FakeClusterManagerConfigurationDocumentClient + clusterManagerDatabase database.ClusterManagerConfigurations + subscriptionsClient *cosmosdb.FakeSubscriptionDocumentClient + subscriptionsDatabase database.Subscriptions + openShiftVersionsClient *cosmosdb.FakeOpenShiftVersionDocumentClient + openShiftVersionsDatabase database.OpenShiftVersions + platformWorkloadIdentityRoleSetsClient *cosmosdb.FakePlatformWorkloadIdentityRoleSetDocumentClient + platformWorkloadIdentityRoleSetsDatabase database.PlatformWorkloadIdentityRoleSets } func newTestInfra(t *testing.T) *testInfra { @@ -179,6 +181,13 @@ func (ti *testInfra) WithOpenShiftVersions() *testInfra { return ti } +func (ti *testInfra) WithPlatformWorkloadIdentityRoleSets() *testInfra { + uuid := deterministicuuid.NewTestUUIDGenerator(8) + ti.platformWorkloadIdentityRoleSetsDatabase, ti.platformWorkloadIdentityRoleSetsClient = testdatabase.NewFakePlatformWorkloadIdentityRoleSets(uuid) + ti.fixture.WithPlatformWorkloadIdentityRoleSets(ti.platformWorkloadIdentityRoleSetsDatabase, uuid) + return ti +} + func (ti *testInfra) WithClusterManagerConfigurations() *testInfra { ti.clusterManagerDatabase, ti.clusterManagerClient = testdatabase.NewFakeClusterManager() ti.fixture.WithClusterManagerConfigurations(ti.clusterManagerDatabase) diff --git a/pkg/frontend/subscriptions_put_test.go b/pkg/frontend/subscriptions_put_test.go index cd236ba816a..7be248dbd9b 100644 --- a/pkg/frontend/subscriptions_put_test.go +++ b/pkg/frontend/subscriptions_put_test.go @@ -244,7 +244,7 @@ func TestPutSubscription(t *testing.T) { t.Fatal(err) } - f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) + f, err := NewFrontend(ctx, ti.audit, ti.log, ti.env, ti.asyncOperationsDatabase, ti.clusterManagerDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, nil, nil, api.APIs, &noop.Noop{}, &noop.Noop{}, nil, nil, nil, nil, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/frontend/validate.go b/pkg/frontend/validate.go index 7167c1add14..0fdf2bef725 100644 --- a/pkg/frontend/validate.go +++ b/pkg/frontend/validate.go @@ -203,14 +203,14 @@ func validateAdminMasterVMSize(vmSize string) error { // validateInstallVersion validates the install version set in the clusterprofile.version // TODO convert this into static validation instead of this receiver function in the validation for frontend. func (f *frontend) validateInstallVersion(ctx context.Context, oc *api.OpenShiftCluster) error { - f.mu.RLock() + f.ocpVersionsMu.RLock() // If this request is from an older API or the user did not specify // the version to install, use the default version. if oc.Properties.ClusterProfile.Version == "" { oc.Properties.ClusterProfile.Version = f.defaultOcpVersion } _, ok := f.enabledOcpVersions[oc.Properties.ClusterProfile.Version] - f.mu.RUnlock() + f.ocpVersionsMu.RUnlock() if !ok || !validate.RxInstallVersion.MatchString(oc.Properties.ClusterProfile.Version) { return api.NewCloudError(http.StatusBadRequest, api.CloudErrorCodeInvalidParameter, "properties.clusterProfile.version", "The requested OpenShift version '%s' is invalid.", oc.Properties.ClusterProfile.Version) diff --git a/pkg/util/version/semver.go b/pkg/util/version/semver.go new file mode 100644 index 00000000000..7b9ac06cee0 --- /dev/null +++ b/pkg/util/version/semver.go @@ -0,0 +1,17 @@ +package version + +// Copyright (c) Microsoft Corporation. +// Licensed under the Apache License 2.0. + +import ( + "github.com/coreos/go-semver/semver" +) + +// CreateSemverFromMinorVersionString takes in a string representing a semantic version number that is +// missing the patch version from the end (ex.: "4.13") and appends a ".0" and returns a semver.Version. +// It results in a panic if v + ".0" does not turn out to be a valid semantic version number. This function +// is useful for applications such as making it easier to compare strings that represent OpenShift minor +// versions. +func CreateSemverFromMinorVersionString(v string) *semver.Version { + return semver.New(v + ".0") +} diff --git a/test/database/check.go b/test/database/check.go index 2c6a4ffbc88..dd1e428a9c9 100644 --- a/test/database/check.go +++ b/test/database/check.go @@ -18,14 +18,15 @@ import ( const deletionTimeSetSentinel = 123456789 type Checker struct { - openshiftClusterDocuments []*api.OpenShiftClusterDocument - subscriptionDocuments []*api.SubscriptionDocument - billingDocuments []*api.BillingDocument - asyncOperationDocuments []*api.AsyncOperationDocument - portalDocuments []*api.PortalDocument - gatewayDocuments []*api.GatewayDocument - openShiftVersionDocuments []*api.OpenShiftVersionDocument - validationResult []*api.ValidationResult + openshiftClusterDocuments []*api.OpenShiftClusterDocument + subscriptionDocuments []*api.SubscriptionDocument + billingDocuments []*api.BillingDocument + asyncOperationDocuments []*api.AsyncOperationDocument + portalDocuments []*api.PortalDocument + gatewayDocuments []*api.GatewayDocument + openShiftVersionDocuments []*api.OpenShiftVersionDocument + platformWorkloadIdentityRoleSetDocuments []*api.PlatformWorkloadIdentityRoleSetDocument + validationResult []*api.ValidationResult } func NewChecker() *Checker { @@ -109,6 +110,17 @@ func (f *Checker) AddOpenShiftVersionDocuments(docs ...*api.OpenShiftVersionDocu } } +func (f *Checker) AddPlatformWorkloadIdentityRoleSetDocuments(docs ...*api.PlatformWorkloadIdentityRoleSetDocument) { + for _, doc := range docs { + docCopy, err := deepCopy(doc) + if err != nil { + panic(err) + } + + f.platformWorkloadIdentityRoleSetDocuments = append(f.platformWorkloadIdentityRoleSetDocuments, docCopy.(*api.PlatformWorkloadIdentityRoleSetDocument)) + } +} + func (f *Checker) AddValidationResult(docs ...*api.ValidationResult) { for _, doc := range docs { docCopy, err := deepCopy(doc) @@ -273,3 +285,27 @@ func (f *Checker) CheckOpenShiftVersions(versions *cosmosdb.FakeOpenShiftVersion return errs } + +func (f *Checker) CheckPlatformWorkloadIdentityRoleSets(roleSets *cosmosdb.FakePlatformWorkloadIdentityRoleSetDocumentClient) (errs []error) { + ctx := context.Background() + + all, err := roleSets.ListAll(ctx, nil) + if err != nil { + return []error{err} + } + + sort.Slice(all.PlatformWorkloadIdentityRoleSetDocuments, func(i, j int) bool { + return all.PlatformWorkloadIdentityRoleSetDocuments[i].ID < all.PlatformWorkloadIdentityRoleSetDocuments[j].ID + }) + + if len(f.platformWorkloadIdentityRoleSetDocuments) != 0 && len(all.PlatformWorkloadIdentityRoleSetDocuments) == len(f.platformWorkloadIdentityRoleSetDocuments) { + diff := deep.Equal(all.PlatformWorkloadIdentityRoleSetDocuments, f.platformWorkloadIdentityRoleSetDocuments) + for _, i := range diff { + errs = append(errs, errors.New(i)) + } + } else if len(all.PlatformWorkloadIdentityRoleSetDocuments) != 0 || len(f.platformWorkloadIdentityRoleSetDocuments) != 0 { + errs = append(errs, fmt.Errorf("role sets length different, %d vs %d", len(all.PlatformWorkloadIdentityRoleSetDocuments), len(f.platformWorkloadIdentityRoleSetDocuments))) + } + + return errs +} diff --git a/test/database/fixtures.go b/test/database/fixtures.go index 53c6ed402ef..0341e27f71a 100644 --- a/test/database/fixtures.go +++ b/test/database/fixtures.go @@ -12,25 +12,28 @@ import ( ) type Fixture struct { - openshiftClusterDocuments []*api.OpenShiftClusterDocument - subscriptionDocuments []*api.SubscriptionDocument - billingDocuments []*api.BillingDocument - asyncOperationDocuments []*api.AsyncOperationDocument - portalDocuments []*api.PortalDocument - gatewayDocuments []*api.GatewayDocument - openShiftVersionDocuments []*api.OpenShiftVersionDocument - clusterManagerConfigurationDocuments []*api.ClusterManagerConfigurationDocument - - openShiftClustersDatabase database.OpenShiftClusters - billingDatabase database.Billing - subscriptionsDatabase database.Subscriptions - asyncOperationsDatabase database.AsyncOperations - portalDatabase database.Portal - gatewayDatabase database.Gateway - openShiftVersionsDatabase database.OpenShiftVersions - clusterManagerConfigurationsDatabase database.ClusterManagerConfigurations - - openShiftVersionsUUID uuid.Generator + openshiftClusterDocuments []*api.OpenShiftClusterDocument + subscriptionDocuments []*api.SubscriptionDocument + billingDocuments []*api.BillingDocument + asyncOperationDocuments []*api.AsyncOperationDocument + portalDocuments []*api.PortalDocument + gatewayDocuments []*api.GatewayDocument + openShiftVersionDocuments []*api.OpenShiftVersionDocument + platformWorkloadIdentityRoleSetDocuments []*api.PlatformWorkloadIdentityRoleSetDocument + clusterManagerConfigurationDocuments []*api.ClusterManagerConfigurationDocument + + openShiftClustersDatabase database.OpenShiftClusters + billingDatabase database.Billing + subscriptionsDatabase database.Subscriptions + asyncOperationsDatabase database.AsyncOperations + portalDatabase database.Portal + gatewayDatabase database.Gateway + openShiftVersionsDatabase database.OpenShiftVersions + platformWorkloadIdentityRoleSetsDatabase database.PlatformWorkloadIdentityRoleSets + clusterManagerConfigurationsDatabase database.ClusterManagerConfigurations + + openShiftVersionsUUID uuid.Generator + platformWorkloadIdentityRoleSetsUUID uuid.Generator } func NewFixture() *Fixture { @@ -78,6 +81,12 @@ func (f *Fixture) WithOpenShiftVersions(db database.OpenShiftVersions, uuid uuid return f } +func (f *Fixture) WithPlatformWorkloadIdentityRoleSets(db database.PlatformWorkloadIdentityRoleSets, uuid uuid.Generator) *Fixture { + f.platformWorkloadIdentityRoleSetsDatabase = db + f.platformWorkloadIdentityRoleSetsUUID = uuid + return f +} + func (f *Fixture) AddOpenShiftClusterDocuments(docs ...*api.OpenShiftClusterDocument) { for _, doc := range docs { docCopy, err := deepCopy(doc) @@ -155,6 +164,17 @@ func (f *Fixture) AddOpenShiftVersionDocuments(docs ...*api.OpenShiftVersionDocu } } +func (f *Fixture) AddPlatformWorkloadIdentityRoleSetDocuments(docs ...*api.PlatformWorkloadIdentityRoleSetDocument) { + for _, doc := range docs { + docCopy, err := deepCopy(doc) + if err != nil { + panic(err) + } + + f.platformWorkloadIdentityRoleSetDocuments = append(f.platformWorkloadIdentityRoleSetDocuments, docCopy.(*api.PlatformWorkloadIdentityRoleSetDocument)) + } +} + func (f *Fixture) AddClusterManagerConfigurationDocuments(docs ...*api.ClusterManagerConfigurationDocument) { for _, doc := range docs { docCopy, err := deepCopy(doc) @@ -224,6 +244,16 @@ func (f *Fixture) Create() error { } } + for _, i := range f.platformWorkloadIdentityRoleSetDocuments { + if i.ID == "" { + i.ID = f.platformWorkloadIdentityRoleSetsDatabase.NewUUID() + } + _, err := f.platformWorkloadIdentityRoleSetsDatabase.Create(ctx, i) + if err != nil { + return err + } + } + for _, i := range f.clusterManagerConfigurationDocuments { if i.ID == "" { i.ID = f.clusterManagerConfigurationsDatabase.NewUUID() diff --git a/test/database/inmemory.go b/test/database/inmemory.go index 7e8bb8fc554..66907cf69ed 100644 --- a/test/database/inmemory.go +++ b/test/database/inmemory.go @@ -72,6 +72,12 @@ func NewFakeOpenShiftVersions(uuid uuid.Generator) (db database.OpenShiftVersion return db, client } +func NewFakePlatformWorkloadIdentityRoleSets(uuid uuid.Generator) (db database.PlatformWorkloadIdentityRoleSets, client *cosmosdb.FakePlatformWorkloadIdentityRoleSetDocumentClient) { + client = cosmosdb.NewFakePlatformWorkloadIdentityRoleSetDocumentClient(jsonHandle) + db = database.NewPlatformWorkloadIdentityRoleSetsWithProvidedClient(client, uuid) + return db, client +} + func NewFakeClusterManager() (db database.ClusterManagerConfigurations, client *cosmosdb.FakeClusterManagerConfigurationDocumentClient) { uuid := deterministicuuid.NewTestUUIDGenerator(deterministicuuid.CLUSTERMANAGER) client = cosmosdb.NewFakeClusterManagerConfigurationDocumentClient(jsonHandle)