Skip to content

Commit 0a8bce3

Browse files
authored
CLOUDP-299197: AtlasNetworkContainer CRD Controller (#2100)
* 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]>
1 parent 94a2815 commit 0a8bce3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+3822
-13
lines changed

.github/workflows/test-e2e.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ jobs:
185185
"flex",
186186
"ip-access-list",
187187
"dry-run",
188+
"networkcontainer-controller",
188189
]
189190
steps:
190191
- name: Get repo files from cache

.mockery.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ packages:
1616
github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/privateendpoint:
1717
github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/maintenancewindow:
1818
github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/encryptionatrest:
19+
github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/networkcontainer:

PROJECT

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,13 @@ resources:
126126
kind: AtlasIPAccessList
127127
path: github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1
128128
version: v1
129+
- api:
130+
crdVersion: v1
131+
namespaced: true
132+
controller: true
133+
domain: mongodb.com
134+
group: atlas
135+
kind: AtlasNetworkContainer
136+
path: github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1
137+
version: v1
129138
version: "3"

api/condition.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@ const (
106106
IPAccessListReady ConditionType = "IPAccessListReady"
107107
)
108108

109+
// Atlas Network Container condition types
110+
const (
111+
NetworkContainerReady ConditionType = "NetworkContainerReady"
112+
)
113+
109114
// Generic condition type
110115
const (
111116
ResourceVersionStatus ConditionType = "ResourceVersionIsValid"

api/v1/atlasnetworkcontainer_types.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
Copyright 2025 MongoDB.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1
18+
19+
import (
20+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
22+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/api"
23+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1/status"
24+
)
25+
26+
func init() {
27+
SchemeBuilder.Register(&AtlasNetworkContainer{}, &AtlasNetworkContainerList{})
28+
}
29+
30+
// AtlasNetworkContainer is the Schema for the AtlasNetworkContainer API
31+
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
32+
// +kubebuilder:object:root=true
33+
// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status`
34+
// +kubebuilder:printcolumn:name="Provider",type=string,JSONPath=`.spec.provider`
35+
// +kubebuilder:printcolumn:name="Id",type=string,JSONPath=`.status.id`
36+
// +kubebuilder:subresource:status
37+
// +groupName:=atlas.mongodb.com
38+
// +kubebuilder:resource:categories=atlas,shortName=anc
39+
type AtlasNetworkContainer struct {
40+
metav1.TypeMeta `json:",inline"`
41+
metav1.ObjectMeta `json:"metadata,omitempty"`
42+
43+
Spec AtlasNetworkContainerSpec `json:"spec,omitempty"`
44+
Status status.AtlasNetworkContainerStatus `json:"status,omitempty"`
45+
}
46+
47+
//+kubebuilder:object:root=true
48+
49+
// AtlasNetworkContainerList contains a list of AtlasNetworkContainer
50+
type AtlasNetworkContainerList struct {
51+
metav1.TypeMeta `json:",inline"`
52+
metav1.ListMeta `json:"metadata,omitempty"`
53+
Items []AtlasNetworkContainer `json:"items"`
54+
}
55+
56+
// +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"
57+
// +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"
58+
// +kubebuilder:validation:XValidation:rule="(self.provider == 'GCP' && !has(self.region)) || (self.provider != 'GCP')",message="must not set region for GCP containers"
59+
// +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"
60+
// +kubebuilder:validation:XValidation:rule="(self.id == oldSelf.id) || (!has(self.id) && !has(oldSelf.id))",message="id is immutable"
61+
// +kubebuilder:validation:XValidation:rule="(self.region == oldSelf.region) || (!has(self.region) && !has(oldSelf.region))",message="region is immutable"
62+
63+
// AtlasNetworkContainerSpec defines the desired state of an AtlasNetworkContainer
64+
type AtlasNetworkContainerSpec struct {
65+
ProjectDualReference `json:",inline"`
66+
67+
// Provider is the name of the cloud provider hosting the network container
68+
// +kubebuilder:validation:Enum=AWS;GCP;AZURE
69+
// +kubebuilder:validation:Required
70+
Provider string `json:"provider"`
71+
72+
AtlasNetworkContainerConfig `json:",inline"`
73+
}
74+
75+
// AtlasNetworkContainerConfig defines the Atlas specifics of the desired state of a Network Container
76+
type AtlasNetworkContainerConfig struct {
77+
// ID is the container identified for an already existent network container to be managed by the operator.
78+
// This field can be used in conjunction with cidrBlock to update the cidrBlock of an existing container.
79+
// This field is immutable.
80+
// +optional
81+
ID string `json:"id,omitempty"`
82+
83+
// ContainerRegion is the provider region name of Atlas network peer container in Atlas region format
84+
// This is required by AWS and Azure, but not used by GCP.
85+
// This field is immutable, Atlas does not admit network container changes.
86+
// +optional
87+
Region string `json:"region,omitempty"`
88+
89+
// Atlas CIDR. It needs to be set if ContainerID is not set.
90+
// +optional
91+
CIDRBlock string `json:"cidrBlock"`
92+
}
93+
94+
func (np *AtlasNetworkContainer) GetStatus() api.Status {
95+
return np.Status
96+
}
97+
98+
func (np *AtlasNetworkContainer) Credentials() *api.LocalObjectReference {
99+
return np.Spec.ConnectionSecret
100+
}
101+
102+
func (np *AtlasNetworkContainer) ProjectDualRef() *ProjectDualReference {
103+
return &np.Spec.ProjectDualReference
104+
}
105+
106+
func (np *AtlasNetworkContainer) UpdateStatus(conditions []api.Condition, options ...api.Option) {
107+
np.Status.Conditions = conditions
108+
np.Status.ObservedGeneration = np.ObjectMeta.Generation
109+
110+
for _, o := range options {
111+
v := o.(status.AtlasNetworkContainerStatusOption)
112+
v(&np.Status)
113+
}
114+
}
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package v1
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
"k8s.io/apimachinery/pkg/runtime"
9+
10+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1/common"
11+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1/provider"
12+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/cel"
13+
)
14+
15+
func TestContainerCELChecks(t *testing.T) {
16+
for _, tc := range []struct {
17+
title string
18+
old, obj *AtlasNetworkContainer
19+
expectedErrors []string
20+
}{
21+
{
22+
title: "GCP fails with a region",
23+
obj: &AtlasNetworkContainer{
24+
Spec: AtlasNetworkContainerSpec{
25+
Provider: string(provider.ProviderGCP),
26+
AtlasNetworkContainerConfig: AtlasNetworkContainerConfig{
27+
Region: "some-region",
28+
},
29+
},
30+
},
31+
expectedErrors: []string{"spec: Invalid value: \"object\": must not set region for GCP containers"},
32+
},
33+
{
34+
title: "GCP succeeds without a region",
35+
obj: &AtlasNetworkContainer{
36+
Spec: AtlasNetworkContainerSpec{
37+
Provider: string(provider.ProviderGCP),
38+
},
39+
},
40+
},
41+
{
42+
title: "AWS succeeds with a region",
43+
obj: &AtlasNetworkContainer{
44+
Spec: AtlasNetworkContainerSpec{
45+
Provider: string(provider.ProviderAWS),
46+
AtlasNetworkContainerConfig: AtlasNetworkContainerConfig{
47+
Region: "some-region",
48+
},
49+
},
50+
},
51+
},
52+
{
53+
title: "Azure succeeds with a region",
54+
obj: &AtlasNetworkContainer{
55+
Spec: AtlasNetworkContainerSpec{
56+
Provider: string(provider.ProviderAzure),
57+
AtlasNetworkContainerConfig: AtlasNetworkContainerConfig{
58+
Region: "some-region",
59+
},
60+
},
61+
},
62+
},
63+
{
64+
title: "AWS fails without a region",
65+
obj: &AtlasNetworkContainer{
66+
Spec: AtlasNetworkContainerSpec{
67+
Provider: string(provider.ProviderAWS),
68+
},
69+
},
70+
expectedErrors: []string{"spec: Invalid value: \"object\": must set region for AWS and Azure containers"},
71+
},
72+
{
73+
title: "Azure fails without a region",
74+
obj: &AtlasNetworkContainer{
75+
Spec: AtlasNetworkContainerSpec{
76+
Provider: string(provider.ProviderAzure),
77+
},
78+
},
79+
expectedErrors: []string{"spec: Invalid value: \"object\": must set region for AWS and Azure containers"},
80+
},
81+
{
82+
title: "ID cannot be changed",
83+
old: &AtlasNetworkContainer{
84+
Spec: AtlasNetworkContainerSpec{
85+
Provider: string(provider.ProviderGCP),
86+
AtlasNetworkContainerConfig: AtlasNetworkContainerConfig{
87+
ID: "old-id",
88+
},
89+
},
90+
},
91+
obj: &AtlasNetworkContainer{
92+
Spec: AtlasNetworkContainerSpec{
93+
Provider: string(provider.ProviderGCP),
94+
AtlasNetworkContainerConfig: AtlasNetworkContainerConfig{
95+
ID: "new-id",
96+
},
97+
},
98+
},
99+
expectedErrors: []string{"spec: Invalid value: \"object\": id is immutable"},
100+
},
101+
{
102+
title: "ID can be unset",
103+
old: &AtlasNetworkContainer{
104+
Spec: AtlasNetworkContainerSpec{
105+
Provider: string(provider.ProviderGCP),
106+
},
107+
},
108+
obj: &AtlasNetworkContainer{
109+
Spec: AtlasNetworkContainerSpec{
110+
Provider: string(provider.ProviderGCP),
111+
},
112+
},
113+
},
114+
{
115+
title: "ID can be set",
116+
obj: &AtlasNetworkContainer{
117+
Spec: AtlasNetworkContainerSpec{
118+
Provider: string(provider.ProviderGCP),
119+
AtlasNetworkContainerConfig: AtlasNetworkContainerConfig{
120+
ID: "new-id",
121+
},
122+
},
123+
},
124+
},
125+
{
126+
title: "Region cannot be changed",
127+
old: &AtlasNetworkContainer{
128+
Spec: AtlasNetworkContainerSpec{
129+
Provider: string(provider.ProviderAWS),
130+
AtlasNetworkContainerConfig: AtlasNetworkContainerConfig{
131+
Region: "old-region",
132+
},
133+
},
134+
},
135+
obj: &AtlasNetworkContainer{
136+
Spec: AtlasNetworkContainerSpec{
137+
Provider: string(provider.ProviderAWS),
138+
AtlasNetworkContainerConfig: AtlasNetworkContainerConfig{
139+
Region: "new-region",
140+
},
141+
},
142+
},
143+
expectedErrors: []string{"spec: Invalid value: \"object\": region is immutable"},
144+
},
145+
{
146+
title: "Region can be unset (for GCP)",
147+
old: &AtlasNetworkContainer{
148+
Spec: AtlasNetworkContainerSpec{
149+
Provider: string(provider.ProviderGCP),
150+
},
151+
},
152+
obj: &AtlasNetworkContainer{
153+
Spec: AtlasNetworkContainerSpec{
154+
Provider: string(provider.ProviderGCP),
155+
},
156+
},
157+
},
158+
{
159+
title: "Region can be set",
160+
obj: &AtlasNetworkContainer{
161+
Spec: AtlasNetworkContainerSpec{
162+
Provider: string(provider.ProviderAWS),
163+
AtlasNetworkContainerConfig: AtlasNetworkContainerConfig{
164+
Region: "new-region",
165+
},
166+
},
167+
},
168+
},
169+
} {
170+
t.Run(tc.title, func(t *testing.T) {
171+
// inject a project to avoid other CEL validations being hit
172+
tc.obj.Spec.ProjectRef = &common.ResourceRefNamespaced{Name: "some-project"}
173+
unstructuredOldObject, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&tc.old)
174+
require.NoError(t, err)
175+
unstructuredObject, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&tc.obj)
176+
require.NoError(t, err)
177+
178+
crdPath := "../../config/crd/bases/atlas.mongodb.com_atlasnetworkcontainers.yaml"
179+
validator, err := cel.VersionValidatorFromFile(t, crdPath, "v1")
180+
assert.NoError(t, err)
181+
errs := validator(unstructuredObject, unstructuredOldObject)
182+
183+
require.Equal(t, tc.expectedErrors, cel.ErrorListAsStrings(errs))
184+
})
185+
}
186+
}

api/v1/project_reference_cel_test.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ var dualRefCRDs = []struct {
4747
obj: &AtlasIPAccessList{},
4848
filename: "atlas.mongodb.com_atlasipaccesslists.yaml",
4949
},
50+
{
51+
obj: &AtlasNetworkContainer{
52+
Spec: AtlasNetworkContainerSpec{
53+
Provider: "GCP", // Avoid triggering container specific validations
54+
},
55+
},
56+
filename: "atlas.mongodb.com_atlasnetworkcontainers.yaml",
57+
},
5058
}
5159

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

160-
require.Equal(t, len(tc.expectedErrors), len(errs))
161-
162-
for i, err := range errs {
163-
assert.Equal(t, tc.expectedErrors[i], err.Error())
164-
}
168+
require.Equal(t, tc.expectedErrors, cel.ErrorListAsStrings(errs))
165169
})
166170
}
167171
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package status
2+
3+
import "github.com/mongodb/mongodb-atlas-kubernetes/v2/api"
4+
5+
// AtlasNetworkContainerStatus is a status for the AtlasNetworkContainer Custom resource.
6+
// Not the one included in the AtlasProject
7+
type AtlasNetworkContainerStatus struct {
8+
api.Common `json:",inline"`
9+
10+
// ID record the identifier of the container in Atlas
11+
ID string `json:"id,omitempty"`
12+
13+
// Provisioned is true when clusters have been deployed to the container before
14+
// the last reconciliation
15+
Provisioned bool `json:"provisioned,omitempty"`
16+
}
17+
18+
// +kubebuilder:object:generate=false
19+
20+
type AtlasNetworkContainerStatusOption func(s *AtlasNetworkContainerStatus)

0 commit comments

Comments
 (0)