Skip to content

Commit

Permalink
CLOUDP-266544: DatabaseUser independent CRD (#1769)
Browse files Browse the repository at this point in the history
  • Loading branch information
helderjs authored Oct 2, 2024
1 parent 105ee40 commit c56374f
Show file tree
Hide file tree
Showing 29 changed files with 1,113 additions and 252 deletions.
20 changes: 19 additions & 1 deletion config/crd/bases/atlas.mongodb.com_atlasdatabaseusers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,16 @@ spec:
DeleteAfterDate is a timestamp in ISO 8601 date and time format in UTC after which Atlas deletes the user.
The specified date must be in the future and within one week.
type: string
externalProjectRef:
description: ExternalProjectRef holds the Atlas project ID the user
belongs to
properties:
id:
description: ID is the Atlas project ID
type: string
required:
- id
type: object
labels:
description: |-
Labels is an array containing key-value pairs that tag and categorize the database user.
Expand Down Expand Up @@ -212,10 +222,18 @@ spec:
- CUSTOMER
type: string
required:
- projectRef
- roles
- username
type: object
x-kubernetes-validations:
- message: must define only one project reference through externalProjectRef
or projectRef
rule: (has(self.externalProjectRef) && !has(self.projectRef)) || (!has(self.externalProjectRef)
&& has(self.projectRef))
- message: must define a local connection secret when referencing an external
project
rule: (has(self.externalProjectRef) && has(self.connectionSecret)) ||
!has(self.externalProjectRef)
status:
description: AtlasDatabaseUserStatus defines the observed state of AtlasProject
properties:
Expand Down
59 changes: 59 additions & 0 deletions internal/mocks/translation/project_service.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions internal/translation/dbuser/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ func (u *User) clearedSpecClone() *akov2.AtlasDatabaseUserSpec {
return nil
}
clone := *u.AtlasDatabaseUserSpec
clone.Project.Name = ""
clone.Project.Namespace = ""
clone.Project = nil
clone.PasswordSecret = nil
clone.ExternalProjectRef = nil
clone.ConnectionSecret = nil
return &clone
}
Expand Down
12 changes: 8 additions & 4 deletions internal/translation/dbuser/conversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,8 +391,10 @@ func TestDiffSpecs(t *testing.T) {
{Name: "lake1", Type: "DATA_LAKE"},
{Name: "lake2", Type: "DATA_LAKE"},
}
spec.Project.Name = "some-project"
spec.Project.Namespace = "some-namespace"
spec.Project = &common.ResourceRefNamespaced{
Name: "some-project",
Namespace: "some-namespace",
}
spec.PasswordSecret = &common.ResourceRef{Name: "some-secret-ref"}
spec.ConnectionSecret = &api.LocalObjectReference{Name: "some-local-secret-ref"}
return spec
Expand All @@ -407,8 +409,10 @@ func TestDiffSpecs(t *testing.T) {
{Name: "lake1", Type: "DATA_LAKE"},
{Name: "lake2", Type: "DATA_LAKE"},
}
spec.Project.Name = "another-project"
spec.Project.Namespace = "another-namespace"
spec.Project = &common.ResourceRefNamespaced{
Name: "another-project",
Namespace: "another-namespace",
}
spec.PasswordSecret = &common.ResourceRef{Name: "another-secret-ref"}
spec.ConnectionSecret = &api.LocalObjectReference{Name: "another-local-secret-ref"}
return spec
Expand Down
10 changes: 10 additions & 0 deletions internal/translation/project/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

type ProjectService interface {
GetProjectByName(ctx context.Context, name string) (*Project, error)
GetProject(ctx context.Context, ID string) (*Project, error)
CreateProject(ctx context.Context, project *Project) error
DeleteProject(ctx context.Context, project *Project) error
}
Expand All @@ -29,6 +30,15 @@ func (a *ProjectAPI) GetProjectByName(ctx context.Context, name string) (*Projec
return fromAtlas(group), err
}

func (a *ProjectAPI) GetProject(ctx context.Context, ID string) (*Project, error) {
group, _, err := a.projectAPI.GetProject(ctx, ID).Execute()
if err != nil {
return nil, err
}

return fromAtlas(group), err
}

func (a *ProjectAPI) CreateProject(ctx context.Context, project *Project) error {
group, _, err := a.projectAPI.CreateProject(ctx, toAtlas(project)).Execute()
if err != nil {
Expand Down
10 changes: 6 additions & 4 deletions pkg/api/v1/atlasdatabaseuser_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,15 @@ const (
)

// AtlasDatabaseUserSpec defines the desired state of Database User in Atlas
// +kubebuilder:validation:XValidation:rule="(has(self.externalProjectRef) && !has(self.projectRef)) || (!has(self.externalProjectRef) && has(self.projectRef))",message="must define only one project reference through externalProjectRef or projectRef"
// +kubebuilder:validation:XValidation:rule="(has(self.externalProjectRef) && has(self.connectionSecret)) || !has(self.externalProjectRef)",message="must define a local connection secret when referencing an external project"
type AtlasDatabaseUserSpec struct {
api.LocalCredentialHolder `json:",inline"`

// Project is a reference to AtlasProject resource the user belongs to
Project common.ResourceRefNamespaced `json:"projectRef"`
Project *common.ResourceRefNamespaced `json:"projectRef,omitempty"`
// ExternalProjectRef holds the Atlas project ID the user belongs to
ExternalProjectRef *ExternalProjectReference `json:"externalProjectRef,omitempty"`

// DatabaseName is a Database against which Atlas authenticates the user. Default value is 'admin'.
// +kubebuilder:default=admin
Expand Down Expand Up @@ -113,8 +117,6 @@ type AtlasDatabaseUserSpec struct {
X509Type string `json:"x509Type,omitempty"`
}

var _ api.AtlasCustomResource = &AtlasDatabaseUser{}

// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:object:root=true
// +kubebuilder:printcolumn:name="Name",type=string,JSONPath=`.spec.name`
Expand Down Expand Up @@ -255,7 +257,7 @@ func NewDBUser(namespace, name, dbUserName, projectName string) *AtlasDatabaseUs
},
Spec: AtlasDatabaseUserSpec{
Username: dbUserName,
Project: common.ResourceRefNamespaced{Name: projectName},
Project: &common.ResourceRefNamespaced{Name: projectName},
PasswordSecret: &common.ResourceRef{},
Roles: []RoleSpec{},
Scopes: []ScopeSpec{},
Expand Down
157 changes: 157 additions & 0 deletions pkg/api/v1/atlasdatabaseuser_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package v1

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime"

"github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api"
"github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1/common"
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/cel"
)

func TestProjectReference(t *testing.T) {
validator, err := cel.VersionValidatorFromFile(t, "../../../config/crd/bases/atlas.mongodb.com_atlasdatabaseusers.yaml", "v1")
require.NoError(t, err)

tests := map[string]struct {
dbUser *AtlasDatabaseUser
expectedErrors []string
}{
"no project reference is set": {
dbUser: &AtlasDatabaseUser{
Spec: AtlasDatabaseUserSpec{},
},
expectedErrors: []string{"spec: Invalid value: \"object\": must define only one project reference through externalProjectRef or projectRef"},
},
"both project references are set": {
dbUser: &AtlasDatabaseUser{
Spec: AtlasDatabaseUserSpec{
Project: &common.ResourceRefNamespaced{
Name: "my-project",
},
ExternalProjectRef: &ExternalProjectReference{
ID: "my-project-id",
},
},
},
expectedErrors: []string{
"spec: Invalid value: \"object\": must define only one project reference through externalProjectRef or projectRef",
"spec: Invalid value: \"object\": must define a local connection secret when referencing an external project",
},
},
"external project references is set": {
dbUser: &AtlasDatabaseUser{
Spec: AtlasDatabaseUserSpec{
ExternalProjectRef: &ExternalProjectReference{
ID: "my-project-id",
},
},
},
expectedErrors: []string{
"spec: Invalid value: \"object\": must define a local connection secret when referencing an external project",
},
},
"kubernetes project references is set": {
dbUser: &AtlasDatabaseUser{
Spec: AtlasDatabaseUserSpec{
Project: &common.ResourceRefNamespaced{
Name: "my-project",
},
},
},
},
}

for name, tt := range tests {
t.Run(name, func(t *testing.T) {
unstructuredDBUser, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&tt.dbUser)
require.NoError(t, err)

errs := validator(unstructuredDBUser, nil)

require.Equal(t, len(tt.expectedErrors), len(errs))

for i, err := range errs {
assert.Equal(t, tt.expectedErrors[i], err.Error())
}
})
}
}

func TestExternalProjectReferenceConnectionSecret(t *testing.T) {
validator, err := cel.VersionValidatorFromFile(t, "../../../config/crd/bases/atlas.mongodb.com_atlasdatabaseusers.yaml", "v1")
require.NoError(t, err)

tests := map[string]struct {
dbUser *AtlasDatabaseUser
expectedErrors []string
}{
"external project references is set without connection secret": {
dbUser: &AtlasDatabaseUser{
Spec: AtlasDatabaseUserSpec{
ExternalProjectRef: &ExternalProjectReference{
ID: "my-project-id",
},
},
},
expectedErrors: []string{
"spec: Invalid value: \"object\": must define a local connection secret when referencing an external project",
},
},
"external project references is set with connection secret": {
dbUser: &AtlasDatabaseUser{
Spec: AtlasDatabaseUserSpec{
ExternalProjectRef: &ExternalProjectReference{
ID: "my-project-id",
},
LocalCredentialHolder: api.LocalCredentialHolder{
ConnectionSecret: &api.LocalObjectReference{
Name: "my-dbuser-connection-secret",
},
},
},
},
},
"kubernetes project references is set without connection secret": {
dbUser: &AtlasDatabaseUser{
Spec: AtlasDatabaseUserSpec{
Project: &common.ResourceRefNamespaced{
Name: "my-project",
},
},
},
},
"kubernetes project references is set with connection secret": {
dbUser: &AtlasDatabaseUser{
Spec: AtlasDatabaseUserSpec{
Project: &common.ResourceRefNamespaced{
Name: "my-project",
},
LocalCredentialHolder: api.LocalCredentialHolder{
ConnectionSecret: &api.LocalObjectReference{
Name: "my-dbuser-connection-secret",
},
},
},
},
},
}

for name, tt := range tests {
t.Run(name, func(t *testing.T) {
unstructuredDBUser, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&tt.dbUser)
require.NoError(t, err)

errs := validator(unstructuredDBUser, nil)

require.Equal(t, len(tt.expectedErrors), len(errs))

for i, err := range errs {
assert.Equal(t, tt.expectedErrors[i], err.Error())
}
})
}
}
7 changes: 7 additions & 0 deletions pkg/api/v1/externalreference.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package v1

type ExternalProjectReference struct {
// ID is the Atlas project ID
// +kubebuilder:validation:Required
ID string `json:"id"`
}
Loading

0 comments on commit c56374f

Please sign in to comment.