diff --git a/build/ci/library_owners.json b/build/ci/library_owners.json index c4513c83ae..98b47dd684 100644 --- a/build/ci/library_owners.json +++ b/build/ci/library_owners.json @@ -1,63 +1,64 @@ { - "cloud.google.com/go/kms": "apix-2", - "github.com/AlecAivazis/survey/v2": "apix-2", - "github.com/Azure/azure-sdk-for-go/sdk/azcore": "apix-2", - "github.com/Azure/azure-sdk-for-go/sdk/azidentity": "apix-2", - "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys": "apix-2", - "github.com/Masterminds/semver/v3": "apix-2", - "github.com/PaesslerAG/jsonpath": "apix-2", - "github.com/aws/aws-sdk-go-v2": "apix-2", - "github.com/aws/aws-sdk-go-v2/config": "apix-2", - "github.com/aws/aws-sdk-go-v2/credentials": "apix-2", - "github.com/aws/aws-sdk-go-v2/service/kms": "apix-2", - "github.com/briandowns/spinner": "apix-2", - "github.com/evergreen-ci/shrub": "apix-2", - "github.com/go-test/deep": "apix-2", - "github.com/golang-jwt/jwt/v4": "apix-2", - "github.com/golang/mock": "apix-2", - "github.com/google/go-github/v61": "apix-2", - "github.com/google/uuid": "atlas_kubernetes_team", - "github.com/klauspost/compress": "apix-2", - "github.com/mattn/go-isatty": "apix-2", - "github.com/mongodb-forks/digest": "apix-2", - "github.com/mongodb-labs/cobra2snooty": "apix-2", - "github.com/pelletier/go-toml": "apix-2", - "github.com/Netflix/go-expect": "apix-2", - "github.com/creack/pty": "apix-2", - "github.com/hinshun/vt10x": "apix-2", - "github.com/pkg/browser": "apix-2", - "github.com/spf13/afero": "apix-2", - "github.com/spf13/cobra": "apix-2", - "github.com/spf13/pflag": "apix-2", - "github.com/spf13/viper": "apix-2", - "github.com/stretchr/testify": "apix-2", - "github.com/tangzero/inflector": "apix-2", - "go.mongodb.org/atlas": "apix-2", - "go.mongodb.org/atlas-sdk/v20241113004": "apix-2", - "go.mongodb.org/atlas-sdk/v20240530005": "apix-2", - "go.mongodb.org/mongo-driver": "apix-2", - "golang.org/x/sys": "apix-2", - "golang.org/x/tools": "apix-2", - "google.golang.org/api": "apix-2", - "google.golang.org/protobuf": "apix-2", - "golang.org/x/mod": "apix-2", - "gopkg.in/yaml.v3": "apix-2", - "github.com/mongodb/mongodb-atlas-kubernetes/v2": "atlas_kubernetes_team", - "k8s.io/api": "atlas_kubernetes_team", - "k8s.io/apimachinery": "atlas_kubernetes_team", - "k8s.io/apiserver": "atlas_kubernetes_team", - "k8s.io/client-go": "atlas_kubernetes_team", - "k8s.io/apiextensions-apiserver": "atlas_kubernetes_team", - "sigs.k8s.io/yaml": "atlas_kubernetes_team", - "sigs.k8s.io/controller-runtime": "atlas_kubernetes_team", - "sigs.k8s.io/kind": "atlas_kubernetes_team", - "golang.org/x/exp": "atlas_kubernetes_team", - "github.com/denisbrodbeck/machineid": "apix-2", - "github.com/shirou/gopsutil/v4": "apix-2", - "go.opentelemetry.io/otel": "apix-2", - "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc": "apix-2", - "go.opentelemetry.io/otel/sdk": "apix-2", - "go.opentelemetry.io/otel/trace": "apix-2", - "google.golang.org/grpc": "apix-2", - "github.com/mholt/archives": "apix-2" + "cloud.google.com/go/kms": "apix-2", + "github.com/AlecAivazis/survey/v2": "apix-2", + "github.com/Azure/azure-sdk-for-go/sdk/azcore": "apix-2", + "github.com/Azure/azure-sdk-for-go/sdk/azidentity": "apix-2", + "github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys": "apix-2", + "github.com/Masterminds/semver/v3": "apix-2", + "github.com/PaesslerAG/jsonpath": "apix-2", + "github.com/aws/aws-sdk-go-v2": "apix-2", + "github.com/aws/aws-sdk-go-v2/config": "apix-2", + "github.com/aws/aws-sdk-go-v2/credentials": "apix-2", + "github.com/aws/aws-sdk-go-v2/service/kms": "apix-2", + "github.com/briandowns/spinner": "apix-2", + "github.com/evergreen-ci/shrub": "apix-2", + "github.com/go-test/deep": "apix-2", + "github.com/golang-jwt/jwt/v4": "apix-2", + "github.com/golang/mock": "apix-2", + "github.com/google/go-github/v61": "apix-2", + "github.com/google/uuid": "atlas_kubernetes_team", + "github.com/klauspost/compress": "apix-2", + "github.com/mattn/go-isatty": "apix-2", + "github.com/mongodb-forks/digest": "apix-2", + "github.com/mongodb-labs/cobra2snooty": "apix-2", + "github.com/pelletier/go-toml": "apix-2", + "github.com/Netflix/go-expect": "apix-2", + "github.com/creack/pty": "apix-2", + "github.com/hinshun/vt10x": "apix-2", + "github.com/pkg/browser": "apix-2", + "github.com/spf13/afero": "apix-2", + "github.com/spf13/cobra": "apix-2", + "github.com/spf13/pflag": "apix-2", + "github.com/spf13/viper": "apix-2", + "github.com/stretchr/testify": "apix-2", + "github.com/tangzero/inflector": "apix-2", + "go.mongodb.org/atlas": "apix-2", + "go.mongodb.org/atlas-sdk/v20241113004": "apix-2", + "go.mongodb.org/atlas-sdk/v20240530005": "apix-2", + "go.mongodb.org/atlas-sdk/v20241113001": "apix-2", + "go.mongodb.org/mongo-driver": "apix-2", + "golang.org/x/sys": "apix-2", + "golang.org/x/tools": "apix-2", + "google.golang.org/api": "apix-2", + "google.golang.org/protobuf": "apix-2", + "golang.org/x/mod": "apix-2", + "gopkg.in/yaml.v3": "apix-2", + "github.com/mongodb/mongodb-atlas-kubernetes/v2": "atlas_kubernetes_team", + "k8s.io/api": "atlas_kubernetes_team", + "k8s.io/apimachinery": "atlas_kubernetes_team", + "k8s.io/apiserver": "atlas_kubernetes_team", + "k8s.io/client-go": "atlas_kubernetes_team", + "k8s.io/apiextensions-apiserver": "atlas_kubernetes_team", + "sigs.k8s.io/yaml": "atlas_kubernetes_team", + "sigs.k8s.io/controller-runtime": "atlas_kubernetes_team", + "sigs.k8s.io/kind": "atlas_kubernetes_team", + "golang.org/x/exp": "atlas_kubernetes_team", + "github.com/denisbrodbeck/machineid": "apix-2", + "github.com/shirou/gopsutil/v4": "apix-2", + "go.opentelemetry.io/otel": "apix-2", + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc": "apix-2", + "go.opentelemetry.io/otel/sdk": "apix-2", + "go.opentelemetry.io/otel/trace": "apix-2", + "google.golang.org/grpc": "apix-2", + "github.com/mholt/archives": "apix-2" } diff --git a/internal/kubernetes/operator/config_exporter.go b/internal/kubernetes/operator/config_exporter.go index 2d6350646e..c4cf7336eb 100644 --- a/internal/kubernetes/operator/config_exporter.go +++ b/internal/kubernetes/operator/config_exporter.go @@ -123,7 +123,6 @@ func (e *ConfigExporter) WithIndependentResources(enabled bool) *ConfigExporter e.independentResources = enabled return e } - func (e *ConfigExporter) Run() (string, error) { // TODO: Add REST to OPERATOR entities matcher output := bytes.NewBufferString(yamlSeparator) @@ -184,6 +183,7 @@ func (e *ConfigExporter) Run() (string, error) { return output.String(), nil } +//nolint:gocyclo func (e *ConfigExporter) exportProject() ([]runtime.Object, string, error) { atlasProject, err := e.dataProvider.Project(e.projectID) if err != nil { @@ -256,6 +256,26 @@ func (e *ConfigExporter) exportProject() ([]runtime.Object, string, error) { } } + // Independent custom roles (AtlasCustomRole CR) + if e.featureValidator.IsResourceSupported(features.ResourceAtlasCustomRole) { + roles, err := project.BuildCustomRoles(e.dataProvider, project.CustomRolesRequest{ + ProjectID: e.projectID, + ProjectName: projectData.Project.Name, + TargetNamespace: e.targetNamespace, + Version: e.operatorVersion, + Credentials: credentialsName, + IsIndependent: e.independentResources, + Dict: e.dictionaryForAtlasNames, + }) + if err != nil { + return nil, "", err + } + + for i := range len(roles) { + r = append(r, &roles[i]) + } + } + // DB users usersData, relatedSecrets, err := dbusers.BuildDBUsers( e.dataProvider, diff --git a/internal/kubernetes/operator/features/crds.go b/internal/kubernetes/operator/features/crds.go index 6d5eed556d..e477d5d6c0 100644 --- a/internal/kubernetes/operator/features/crds.go +++ b/internal/kubernetes/operator/features/crds.go @@ -42,6 +42,7 @@ const ( ResourceAtlasStreamConnection = "atlasstreamconnections" ResourceAtlasBackupCompliancePolicy = "atlasbackupcompliancepolicies" ResourceAtlasPrivateEndpoint = "atlasprivateendpoints" + ResourceAtlasCustomRole = "atlascustomroles" ) var ( @@ -92,6 +93,7 @@ var ( resource{ResourceAtlasStreamConnection, NopPatcher()}, resource{ResourceAtlasBackupCompliancePolicy, NopPatcher()}, resource{ResourceAtlasPrivateEndpoint, NopPatcher()}, + resource{ResourceAtlasCustomRole, NopPatcher()}, }, } ) diff --git a/internal/kubernetes/operator/features/validator.go b/internal/kubernetes/operator/features/validator.go index a94a996d26..4396862895 100644 --- a/internal/kubernetes/operator/features/validator.go +++ b/internal/kubernetes/operator/features/validator.go @@ -18,5 +18,5 @@ package features type FeatureValidator interface { IsResourceSupported(resourceName string) bool - FeatureExist(resourceName, version string) bool + FeatureExist(resourceName, path string) bool } diff --git a/internal/kubernetes/operator/project/customroles.go b/internal/kubernetes/operator/project/customroles.go new file mode 100644 index 0000000000..b36760c7b2 --- /dev/null +++ b/internal/kubernetes/operator/project/customroles.go @@ -0,0 +1,124 @@ +// Copyright 2024 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package project + +import ( + "fmt" + + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/kubernetes/operator/features" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/kubernetes/operator/resources" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/pointer" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/store" + akoapi "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api" + akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1" + akov2common "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1/common" + akov2status "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1/status" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type CustomRolesRequest struct { + ProjectName string + ProjectID string + TargetNamespace string + Credentials string + Version string + IsIndependent bool + Dict map[string]string +} + +func BuildCustomRoles(provider store.DatabaseRoleLister, request CustomRolesRequest) ([]akov2.AtlasCustomRole, error) { + roles, err := provider.DatabaseRoles(request.ProjectID) + if err != nil { + return nil, err + } + if roles == nil { + return nil, nil + } + + result := make([]akov2.AtlasCustomRole, 0, len(roles)) + + for rIdx := range roles { + role := &roles[rIdx] + + inhRoles := make([]akov2.Role, 0, len(role.GetInheritedRoles())) + for _, rl := range role.GetInheritedRoles() { + inhRoles = append(inhRoles, akov2.Role{ + Name: rl.Role, + Database: rl.Db, + }) + } + + actions := make([]akov2.Action, 0, len(role.GetActions())) + for _, action := range role.GetActions() { + r := make([]akov2.Resource, 0, len(action.GetResources())) + for _, res := range action.GetResources() { + r = append(r, akov2.Resource{ + Cluster: pointer.Get(res.Cluster), + Database: pointer.Get(res.Db), + Collection: pointer.Get(res.Collection), + }) + } + actions = append(actions, akov2.Action{ + Name: action.Action, + Resources: r, + }) + } + + akoRole := akov2.AtlasCustomRole{ + TypeMeta: metav1.TypeMeta{ + Kind: "AtlasCustomRole", + APIVersion: "atlas.mongodb.com/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: resources.NormalizeAtlasName( + fmt.Sprintf("%s-custom-role-%s", + request.ProjectName, + role.RoleName), + request.Dict), + Namespace: request.TargetNamespace, + Labels: map[string]string{ + features.ResourceVersion: request.Version, + }, + }, + Spec: akov2.AtlasCustomRoleSpec{ + Role: akov2.CustomRole{ + Name: role.RoleName, + InheritedRoles: inhRoles, + Actions: actions, + }, + }, + Status: akov2status.AtlasCustomRoleStatus{ + Common: akoapi.Common{Conditions: []akoapi.Condition{}}, + }, + } + if request.IsIndependent { + akoRole.Spec.ExternalProjectIDRef = &akov2.ExternalProjectReference{ + ID: request.ProjectID, + } + akoRole.Spec.LocalCredentialHolder = akoapi.LocalCredentialHolder{ + ConnectionSecret: &akoapi.LocalObjectReference{ + Name: resources.NormalizeAtlasName(request.Credentials, request.Dict), + }, + } + } else { + akoRole.Spec.ProjectRef = &akov2common.ResourceRefNamespaced{ + Name: request.ProjectName, + Namespace: request.TargetNamespace, + } + } + result = append(result, akoRole) + } + return result, nil +} diff --git a/internal/kubernetes/operator/project/customroles_test.go b/internal/kubernetes/operator/project/customroles_test.go new file mode 100644 index 0000000000..593b0eadc6 --- /dev/null +++ b/internal/kubernetes/operator/project/customroles_test.go @@ -0,0 +1,272 @@ +// Copyright 2024 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//nolint:all +package project + +import ( + "errors" + "fmt" + "testing" + + "github.com/golang/mock/gomock" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/kubernetes/operator/features" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/kubernetes/operator/resources" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/mocks" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/pointer" + "github.com/mongodb/mongodb-atlas-cli/atlascli/internal/store" + akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1" + akov2common "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1/common" + akov2status "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1/status" + "github.com/stretchr/testify/assert" + atlasv2 "go.mongodb.org/atlas-sdk/v20241113004/admin" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestBuildCustomRoles(t *testing.T) { + projectID := "pid-1" + projectName := "p-1" + targetNamespace := "n-1" + credentialName := "creds" + type args struct { + provider store.DatabaseRoleLister + request CustomRolesRequest + } + tests := []struct { + name string + args args + rolesInAtlas []atlasv2.UserCustomDBRole + errInAtlas error + want []akov2.AtlasCustomRole + wantErr assert.ErrorAssertionFunc + }{ + { + name: "Should return err if provider return error", + args: args{ + request: CustomRolesRequest{ + ProjectName: projectName, + ProjectID: projectID, + TargetNamespace: targetNamespace, + Credentials: credentialName, + Version: "v2.6.0", + IsIndependent: false, + Dict: resources.AtlasNameToKubernetesName(), + }, + }, + errInAtlas: errors.New("no roles found"), + want: nil, + wantErr: func(_ assert.TestingT, err error, i ...any) bool { + return true + }, + }, + { + name: "Should return nil if provider returned empty list of custom roles", + args: args{ + request: CustomRolesRequest{ + ProjectName: projectName, + ProjectID: projectID, + TargetNamespace: targetNamespace, + Credentials: credentialName, + Version: "v2.6.0", + IsIndependent: false, + Dict: resources.AtlasNameToKubernetesName(), + }, + }, + rolesInAtlas: nil, + errInAtlas: nil, + want: nil, + wantErr: func(_ assert.TestingT, err error, i ...any) bool { + return false + }, + }, + { + name: "Should return AKO custom roles if provider returned custom roles", + args: args{ + request: CustomRolesRequest{ + ProjectName: projectName, + ProjectID: projectID, + TargetNamespace: targetNamespace, + Credentials: credentialName, + Version: "v2.6.0", + IsIndependent: false, + Dict: resources.AtlasNameToKubernetesName(), + }, + }, + rolesInAtlas: []atlasv2.UserCustomDBRole{ + { + Actions: &([]atlasv2.DatabasePrivilegeAction{ + { + Action: "test", + Resources: &([]atlasv2.DatabasePermittedNamespaceResource{ + { + Cluster: false, + Collection: "c-1", + Db: "d-1", + }, + }), + }, + }), + InheritedRoles: &([]atlasv2.DatabaseInheritedRole{ + { + Db: "d-1", + Role: "ADMIN", + }, + }), + RoleName: "r-1", + }, + }, + errInAtlas: nil, + want: []akov2.AtlasCustomRole{ + { + TypeMeta: metav1.TypeMeta{ + Kind: "AtlasCustomRole", + APIVersion: "atlas.mongodb.com/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: projectName + "%-custom-role-r-1", + Namespace: targetNamespace, + Labels: map[string]string{ + features.ResourceVersion: "v2.6.0", + }, + }, + Spec: akov2.AtlasCustomRoleSpec{ + Role: akov2.CustomRole{ + Name: "r-1", + InheritedRoles: []akov2.Role{ + { + Name: "ADMIN", + Database: "d-1", + }, + }, + Actions: []akov2.Action{ + { + Name: "test", + Resources: []akov2.Resource{ + { + Cluster: pointer.Get(false), + Database: pointer.Get("d-1"), + Collection: pointer.Get("c-1"), + }, + }, + }, + }, + }, + ProjectRef: &akov2common.ResourceRefNamespaced{ + Name: projectName, + Namespace: targetNamespace, + }, + }, + Status: akov2status.AtlasCustomRoleStatus{}, + }, + }, + wantErr: func(_ assert.TestingT, err error, i ...any) bool { + return false + }, + }, + { + name: "Should return AKO custom roles if provider returned custom roles, as independent", + args: args{ + request: CustomRolesRequest{ + ProjectName: projectName, + ProjectID: projectID, + TargetNamespace: targetNamespace, + Credentials: credentialName, + Version: "v2.6.0", + IsIndependent: true, + Dict: resources.AtlasNameToKubernetesName(), + }, + }, + rolesInAtlas: []atlasv2.UserCustomDBRole{ + { + Actions: &([]atlasv2.DatabasePrivilegeAction{ + { + Action: "test", + Resources: &([]atlasv2.DatabasePermittedNamespaceResource{ + { + Cluster: false, + Collection: "c-1", + Db: "d-1", + }, + }), + }, + }), + InheritedRoles: &([]atlasv2.DatabaseInheritedRole{ + { + Db: "d-1", + Role: "ADMIN", + }, + }), + RoleName: "r-1", + }, + }, + errInAtlas: nil, + want: []akov2.AtlasCustomRole{ + { + TypeMeta: metav1.TypeMeta{ + Kind: "AtlasCustomRole", + APIVersion: "atlas.mongodb.com/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: projectName + "%-custom-role-r-1", + Namespace: targetNamespace, + Labels: map[string]string{ + features.ResourceVersion: "v2.6.0", + }, + }, + Spec: akov2.AtlasCustomRoleSpec{ + Role: akov2.CustomRole{ + Name: "r-1", + InheritedRoles: []akov2.Role{ + { + Name: "ADMIN", + Database: "d-1", + }, + }, + Actions: []akov2.Action{ + { + Name: "test", + Resources: []akov2.Resource{ + { + Cluster: pointer.Get(false), + Database: pointer.Get("d-1"), + Collection: pointer.Get("c-1"), + }, + }, + }, + }, + }, + ExternalProjectIDRef: &akov2.ExternalProjectReference{ID: projectID}, + }, + Status: akov2status.AtlasCustomRoleStatus{}, + }, + }, + wantErr: func(t assert.TestingT, err error, i ...any) bool { + return false + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := gomock.NewController(t) + crStore := mocks.NewMockOperatorProjectStore(c) + crStore.EXPECT().DatabaseRoles(projectID).Return(tt.rolesInAtlas, tt.errInAtlas) + + got, err := BuildCustomRoles(crStore, tt.args.request) + if !tt.wantErr(t, err, fmt.Sprintf("BuildCustomRoles(%v, %v)", tt.args.provider, tt.args.request)) { + return + } + assert.Equalf(t, tt.want, got, "BuildCustomRoles(%v, %v)", tt.args.provider, tt.args.request) + }) + } +} diff --git a/internal/kubernetes/operator/project/project.go b/internal/kubernetes/operator/project/project.go index b6754d6e2e..9142e9856c 100644 --- a/internal/kubernetes/operator/project/project.go +++ b/internal/kubernetes/operator/project/project.go @@ -183,7 +183,7 @@ func BuildAtlasProject(br *AtlasProjectBuildRequest) (*AtlasProjectResult, error result.Secrets = append(result.Secrets, s...) } - if br.Validator.FeatureExist(features.ResourceAtlasProject, featureCustomRoles) { + if br.Validator.FeatureExist(features.ResourceAtlasProject, featureCustomRoles) && !br.Validator.IsResourceSupported(features.ResourceAtlasCustomRole) { customRoles, ferr := buildCustomRoles(br.ProjectStore, br.ProjectID) if ferr != nil { return nil, ferr @@ -275,6 +275,7 @@ func BuildProjectNamedConnectionSecret(credsProvider store.CredentialsGetter, na return secret } +//nolint:revive func buildCustomRoles(crProvider store.DatabaseRoleLister, projectID string) ([]akov2.CustomRole, error) { dbRoles, err := crProvider.DatabaseRoles(projectID) if err != nil { diff --git a/internal/kubernetes/operator/project/project_test.go b/internal/kubernetes/operator/project/project_test.go index b6bf8b9c0d..bd9ecf46a7 100644 --- a/internal/kubernetes/operator/project/project_test.go +++ b/internal/kubernetes/operator/project/project_test.go @@ -577,7 +577,7 @@ func TestBuildAtlasProject(t *testing.T) { }, }, }, - "Can convert Project entity with secrets data wit support for independent resource": { + "Can convert Project entity with secrets data with support for independent resource": { independentResource: true, privateEndpointMock: func(_ *mocks.MockOperatorProjectStore) {}, expectedProject: &akov2.AtlasProject{ @@ -682,29 +682,7 @@ func TestBuildAtlasProject(t *testing.T) { IsRealtimePerformancePanelEnabled: projectSettings.IsRealtimePerformancePanelEnabled, IsSchemaAdvisorEnabled: projectSettings.IsSchemaAdvisorEnabled, }, - CustomRoles: []akov2.CustomRole{ - { - Name: customRoles[0].RoleName, - InheritedRoles: []akov2.Role{ - { - Name: customRoles[0].GetInheritedRoles()[0].Role, - Database: customRoles[0].GetInheritedRoles()[0].Db, - }, - }, - Actions: []akov2.Action{ - { - Name: customRoles[0].GetActions()[0].Action, - Resources: []akov2.Resource{ - { - Cluster: &customRoles[0].GetActions()[0].GetResources()[0].Cluster, - Database: &customRoles[0].GetActions()[0].GetResources()[0].Db, - Collection: &customRoles[0].GetActions()[0].GetResources()[0].Collection, - }, - }, - }, - }, - }, - }, + CustomRoles: nil, Teams: []akov2.Team{ { TeamRef: akov2common.ResourceRefNamespaced{ @@ -744,12 +722,14 @@ func TestBuildAtlasProject(t *testing.T) { projectStore.EXPECT().ProjectSettings(projectID).Return(projectSettings, nil) projectStore.EXPECT().Auditing(projectID).Return(auditing, nil) projectStore.EXPECT().AlertConfigurations(listAlterOpt).Return(alertConfigResult, nil) - projectStore.EXPECT().DatabaseRoles(projectID).Return(customRoles, nil) projectStore.EXPECT().ProjectTeams(projectID, nil).Return(projectTeams, nil) projectStore.EXPECT().TeamByID(orgID, teamID).Return(teams, nil) projectStore.EXPECT().TeamUsers(orgID, teamID).Return(teamUsers, nil) projectStore.EXPECT().DescribeCompliancePolicy(projectID).Return(bcp, nil) tt.privateEndpointMock(projectStore) + if !tt.independentResource { + projectStore.EXPECT().DatabaseRoles(projectID).Return(customRoles, nil) + } featureValidator.EXPECT().FeatureExist(features.ResourceAtlasProject, featureAccessLists).Return(true) featureValidator.EXPECT().FeatureExist(features.ResourceAtlasProject, featureMaintenanceWindows).Return(true) @@ -765,6 +745,7 @@ func TestBuildAtlasProject(t *testing.T) { featureValidator.EXPECT().FeatureExist(features.ResourceAtlasProject, featureTeams).Return(true) featureValidator.EXPECT().FeatureExist(features.ResourceAtlasProject, featureBCP).Return(true) featureValidator.EXPECT().IsResourceSupported(features.ResourceAtlasPrivateEndpoint).Return(tt.independentResource) + featureValidator.EXPECT().IsResourceSupported(features.ResourceAtlasCustomRole).Return(tt.independentResource) projectResult, err := BuildAtlasProject(&AtlasProjectBuildRequest{ ProjectStore: projectStore, diff --git a/test/e2e/atlas/kubernetes_config_generate_test.go b/test/e2e/atlas/kubernetes_config_generate_test.go index b22652c986..25e28e1875 100644 --- a/test/e2e/atlas/kubernetes_config_generate_test.go +++ b/test/e2e/atlas/kubernetes_config_generate_test.go @@ -1042,7 +1042,7 @@ func TestProjectWithCustomRole(t *testing.T) { Name: "FIND", Resources: []akov2.Resource{ { - Database: pointer.Get("test-db "), + Database: pointer.Get("test-db"), Collection: pointer.Get(""), Cluster: pointer.Get(false), }, @@ -1087,10 +1087,64 @@ func TestProjectWithCustomRole(t *testing.T) { objects, err = getK8SEntities(resp) require.NoError(t, err, "should not fail on decode") require.NotEmpty(t, objects) + expectedProject.Spec.CustomRoles = nil + verifyCustomRole(t, objects, &akov2.AtlasCustomRole{ + TypeMeta: metav1.TypeMeta{ + Kind: "AtlasCustomRole", + APIVersion: "atlas.mongodb.com/v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: resources.NormalizeAtlasName(fmt.Sprintf("%s-custom-role-%s", expectedProject.Name, newCustomRole.Name), resources.AtlasNameToKubernetesName()), + Namespace: expectedProject.Namespace, + Labels: map[string]string{ + "mongodb.com/atlas-resource-version": features.LatestOperatorMajorVersion, + }, + }, + Spec: akov2.AtlasCustomRoleSpec{ + ProjectRef: &akov2common.ResourceRefNamespaced{ + Name: expectedProject.Name, + Namespace: expectedProject.Namespace, + }, + Role: akov2.CustomRole{ + Name: "test-role", + Actions: []akov2.Action{ + { + Name: "FIND", + Resources: []akov2.Resource{ + { + Database: pointer.Get("test-db"), + Collection: pointer.Get(""), + Cluster: pointer.Get(false), + }, + }, + }, + }, + }, + }, + Status: akov2status.AtlasCustomRoleStatus{ + Common: akoapi.Common{ + Conditions: []akoapi.Condition{}, + }, + }, + }, + ) checkProject(t, objects, expectedProject) }) } +func verifyCustomRole(t *testing.T, objects []runtime.Object, expectedRole *akov2.AtlasCustomRole) { + t.Helper() + var role *akov2.AtlasCustomRole + for i := range objects { + d, ok := objects[i].(*akov2.AtlasCustomRole) + if ok { + role = d + break + } + } + assert.Equal(t, expectedRole, role) +} + func TestProjectWithIntegration(t *testing.T) { s := InitialSetup(t) cliPath := s.cliPath