Skip to content

Commit

Permalink
CLOUDP-299197: AtlasNetworkContainer CRD Controller (#2100)
Browse files Browse the repository at this point in the history
* Add AtlasNetworkContainer CRD

Signed-off-by: jose.vazquez <[email protected]>

* update rebased helm chart

* Add Network Container controller

* Add deletion protection on network containers

---------

Signed-off-by: jose.vazquez <[email protected]>
  • Loading branch information
josvazg authored Feb 19, 2025
1 parent 94a2815 commit 0a8bce3
Show file tree
Hide file tree
Showing 45 changed files with 3,822 additions and 13 deletions.
1 change: 1 addition & 0 deletions .github/workflows/test-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ jobs:
"flex",
"ip-access-list",
"dry-run",
"networkcontainer-controller",
]
steps:
- name: Get repo files from cache
Expand Down
1 change: 1 addition & 0 deletions .mockery.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ packages:
github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/privateendpoint:
github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/maintenancewindow:
github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/encryptionatrest:
github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/networkcontainer:
9 changes: 9 additions & 0 deletions PROJECT
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,13 @@ resources:
kind: AtlasIPAccessList
path: github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1
version: v1
- api:
crdVersion: v1
namespaced: true
controller: true
domain: mongodb.com
group: atlas
kind: AtlasNetworkContainer
path: github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1
version: v1
version: "3"
5 changes: 5 additions & 0 deletions api/condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ const (
IPAccessListReady ConditionType = "IPAccessListReady"
)

// Atlas Network Container condition types
const (
NetworkContainerReady ConditionType = "NetworkContainerReady"
)

// Generic condition type
const (
ResourceVersionStatus ConditionType = "ResourceVersionIsValid"
Expand Down
114 changes: 114 additions & 0 deletions api/v1/atlasnetworkcontainer_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
Copyright 2025 MongoDB.
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 v1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/mongodb/mongodb-atlas-kubernetes/v2/api"
"github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1/status"
)

func init() {
SchemeBuilder.Register(&AtlasNetworkContainer{}, &AtlasNetworkContainerList{})
}

// AtlasNetworkContainer is the Schema for the AtlasNetworkContainer API
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// +kubebuilder:object:root=true
// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status`
// +kubebuilder:printcolumn:name="Provider",type=string,JSONPath=`.spec.provider`
// +kubebuilder:printcolumn:name="Id",type=string,JSONPath=`.status.id`
// +kubebuilder:subresource:status
// +groupName:=atlas.mongodb.com
// +kubebuilder:resource:categories=atlas,shortName=anc
type AtlasNetworkContainer struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

Spec AtlasNetworkContainerSpec `json:"spec,omitempty"`
Status status.AtlasNetworkContainerStatus `json:"status,omitempty"`
}

//+kubebuilder:object:root=true

// AtlasNetworkContainerList contains a list of AtlasNetworkContainer
type AtlasNetworkContainerList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []AtlasNetworkContainer `json:"items"`
}

// +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"
// +kubebuilder:validation:XValidation:rule="(self.provider == 'GCP' && !has(self.region)) || (self.provider != 'GCP')",message="must not set region for GCP containers"
// +kubebuilder:validation:XValidation:rule="((self.provider == 'AWS' || self.provider == 'AZURE') && has(self.region)) || (self.provider == 'GCP')",message="must set region for AWS and Azure containers"
// +kubebuilder:validation:XValidation:rule="(self.id == oldSelf.id) || (!has(self.id) && !has(oldSelf.id))",message="id is immutable"
// +kubebuilder:validation:XValidation:rule="(self.region == oldSelf.region) || (!has(self.region) && !has(oldSelf.region))",message="region is immutable"

// AtlasNetworkContainerSpec defines the desired state of an AtlasNetworkContainer
type AtlasNetworkContainerSpec struct {
ProjectDualReference `json:",inline"`

// Provider is the name of the cloud provider hosting the network container
// +kubebuilder:validation:Enum=AWS;GCP;AZURE
// +kubebuilder:validation:Required
Provider string `json:"provider"`

AtlasNetworkContainerConfig `json:",inline"`
}

// AtlasNetworkContainerConfig defines the Atlas specifics of the desired state of a Network Container
type AtlasNetworkContainerConfig struct {
// ID is the container identified for an already existent network container to be managed by the operator.
// This field can be used in conjunction with cidrBlock to update the cidrBlock of an existing container.
// This field is immutable.
// +optional
ID string `json:"id,omitempty"`

// ContainerRegion is the provider region name of Atlas network peer container in Atlas region format
// This is required by AWS and Azure, but not used by GCP.
// This field is immutable, Atlas does not admit network container changes.
// +optional
Region string `json:"region,omitempty"`

// Atlas CIDR. It needs to be set if ContainerID is not set.
// +optional
CIDRBlock string `json:"cidrBlock"`
}

func (np *AtlasNetworkContainer) GetStatus() api.Status {
return np.Status
}

func (np *AtlasNetworkContainer) Credentials() *api.LocalObjectReference {
return np.Spec.ConnectionSecret
}

func (np *AtlasNetworkContainer) ProjectDualRef() *ProjectDualReference {
return &np.Spec.ProjectDualReference
}

func (np *AtlasNetworkContainer) UpdateStatus(conditions []api.Condition, options ...api.Option) {
np.Status.Conditions = conditions
np.Status.ObservedGeneration = np.ObjectMeta.Generation

for _, o := range options {
v := o.(status.AtlasNetworkContainerStatusOption)
v(&np.Status)
}
}
186 changes: 186 additions & 0 deletions api/v1/atlasnetworkcontainer_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
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/api/v1/common"
"github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1/provider"
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/cel"
)

func TestContainerCELChecks(t *testing.T) {
for _, tc := range []struct {
title string
old, obj *AtlasNetworkContainer
expectedErrors []string
}{
{
title: "GCP fails with a region",
obj: &AtlasNetworkContainer{
Spec: AtlasNetworkContainerSpec{
Provider: string(provider.ProviderGCP),
AtlasNetworkContainerConfig: AtlasNetworkContainerConfig{
Region: "some-region",
},
},
},
expectedErrors: []string{"spec: Invalid value: \"object\": must not set region for GCP containers"},
},
{
title: "GCP succeeds without a region",
obj: &AtlasNetworkContainer{
Spec: AtlasNetworkContainerSpec{
Provider: string(provider.ProviderGCP),
},
},
},
{
title: "AWS succeeds with a region",
obj: &AtlasNetworkContainer{
Spec: AtlasNetworkContainerSpec{
Provider: string(provider.ProviderAWS),
AtlasNetworkContainerConfig: AtlasNetworkContainerConfig{
Region: "some-region",
},
},
},
},
{
title: "Azure succeeds with a region",
obj: &AtlasNetworkContainer{
Spec: AtlasNetworkContainerSpec{
Provider: string(provider.ProviderAzure),
AtlasNetworkContainerConfig: AtlasNetworkContainerConfig{
Region: "some-region",
},
},
},
},
{
title: "AWS fails without a region",
obj: &AtlasNetworkContainer{
Spec: AtlasNetworkContainerSpec{
Provider: string(provider.ProviderAWS),
},
},
expectedErrors: []string{"spec: Invalid value: \"object\": must set region for AWS and Azure containers"},
},
{
title: "Azure fails without a region",
obj: &AtlasNetworkContainer{
Spec: AtlasNetworkContainerSpec{
Provider: string(provider.ProviderAzure),
},
},
expectedErrors: []string{"spec: Invalid value: \"object\": must set region for AWS and Azure containers"},
},
{
title: "ID cannot be changed",
old: &AtlasNetworkContainer{
Spec: AtlasNetworkContainerSpec{
Provider: string(provider.ProviderGCP),
AtlasNetworkContainerConfig: AtlasNetworkContainerConfig{
ID: "old-id",
},
},
},
obj: &AtlasNetworkContainer{
Spec: AtlasNetworkContainerSpec{
Provider: string(provider.ProviderGCP),
AtlasNetworkContainerConfig: AtlasNetworkContainerConfig{
ID: "new-id",
},
},
},
expectedErrors: []string{"spec: Invalid value: \"object\": id is immutable"},
},
{
title: "ID can be unset",
old: &AtlasNetworkContainer{
Spec: AtlasNetworkContainerSpec{
Provider: string(provider.ProviderGCP),
},
},
obj: &AtlasNetworkContainer{
Spec: AtlasNetworkContainerSpec{
Provider: string(provider.ProviderGCP),
},
},
},
{
title: "ID can be set",
obj: &AtlasNetworkContainer{
Spec: AtlasNetworkContainerSpec{
Provider: string(provider.ProviderGCP),
AtlasNetworkContainerConfig: AtlasNetworkContainerConfig{
ID: "new-id",
},
},
},
},
{
title: "Region cannot be changed",
old: &AtlasNetworkContainer{
Spec: AtlasNetworkContainerSpec{
Provider: string(provider.ProviderAWS),
AtlasNetworkContainerConfig: AtlasNetworkContainerConfig{
Region: "old-region",
},
},
},
obj: &AtlasNetworkContainer{
Spec: AtlasNetworkContainerSpec{
Provider: string(provider.ProviderAWS),
AtlasNetworkContainerConfig: AtlasNetworkContainerConfig{
Region: "new-region",
},
},
},
expectedErrors: []string{"spec: Invalid value: \"object\": region is immutable"},
},
{
title: "Region can be unset (for GCP)",
old: &AtlasNetworkContainer{
Spec: AtlasNetworkContainerSpec{
Provider: string(provider.ProviderGCP),
},
},
obj: &AtlasNetworkContainer{
Spec: AtlasNetworkContainerSpec{
Provider: string(provider.ProviderGCP),
},
},
},
{
title: "Region can be set",
obj: &AtlasNetworkContainer{
Spec: AtlasNetworkContainerSpec{
Provider: string(provider.ProviderAWS),
AtlasNetworkContainerConfig: AtlasNetworkContainerConfig{
Region: "new-region",
},
},
},
},
} {
t.Run(tc.title, func(t *testing.T) {
// inject a project to avoid other CEL validations being hit
tc.obj.Spec.ProjectRef = &common.ResourceRefNamespaced{Name: "some-project"}
unstructuredOldObject, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&tc.old)
require.NoError(t, err)
unstructuredObject, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&tc.obj)
require.NoError(t, err)

crdPath := "../../config/crd/bases/atlas.mongodb.com_atlasnetworkcontainers.yaml"
validator, err := cel.VersionValidatorFromFile(t, crdPath, "v1")
assert.NoError(t, err)
errs := validator(unstructuredObject, unstructuredOldObject)

require.Equal(t, tc.expectedErrors, cel.ErrorListAsStrings(errs))
})
}
}
14 changes: 9 additions & 5 deletions api/v1/project_reference_cel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ var dualRefCRDs = []struct {
obj: &AtlasIPAccessList{},
filename: "atlas.mongodb.com_atlasipaccesslists.yaml",
},
{
obj: &AtlasNetworkContainer{
Spec: AtlasNetworkContainerSpec{
Provider: "GCP", // Avoid triggering container specific validations
},
},
filename: "atlas.mongodb.com_atlasnetworkcontainers.yaml",
},
}

var testCases = []struct {
Expand Down Expand Up @@ -157,11 +165,7 @@ func TestProjectDualReferenceCELValidations(t *testing.T) {
assert.NoError(t, err)
errs := validator(unstructuredObject, unstructuredOldObject)

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

for i, err := range errs {
assert.Equal(t, tc.expectedErrors[i], err.Error())
}
require.Equal(t, tc.expectedErrors, cel.ErrorListAsStrings(errs))
})
}
}
Expand Down
20 changes: 20 additions & 0 deletions api/v1/status/atlasnetworkcontainer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package status

import "github.com/mongodb/mongodb-atlas-kubernetes/v2/api"

// AtlasNetworkContainerStatus is a status for the AtlasNetworkContainer Custom resource.
// Not the one included in the AtlasProject
type AtlasNetworkContainerStatus struct {
api.Common `json:",inline"`

// ID record the identifier of the container in Atlas
ID string `json:"id,omitempty"`

// Provisioned is true when clusters have been deployed to the container before
// the last reconciliation
Provisioned bool `json:"provisioned,omitempty"`
}

// +kubebuilder:object:generate=false

type AtlasNetworkContainerStatusOption func(s *AtlasNetworkContainerStatus)
Loading

0 comments on commit 0a8bce3

Please sign in to comment.