Skip to content

Commit 5f51e17

Browse files
committed
CLOUDP-330314: Disallow project/deployment renaming
When a project is renamed in Kube, the operator will create a new project and leave the original intact, while other resources (such as AtlasDeployment) still track the original clusters in the old project. This leads to bad situations that are difficult to clean up, in particular via the operator. Similarly, when a deployment is renamed in Kube a new deployment is created in Atlas, with the old one left running. Since this is almost definitely not what the user wanted, let's ensure that name changes here are also forbidden.
1 parent 645a0dc commit 5f51e17

6 files changed

+118
-0
lines changed

api/v1/atlasdeployment_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ type AdvancedDeploymentSpec struct {
127127
// Can only contain ASCII letters, numbers, and hyphens.
128128
// +kubebuilder:validation:Required
129129
// +kubebuilder:validation:Pattern:=^[a-zA-Z0-9][a-zA-Z0-9-]*$
130+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Name cannot be modified after deployment creation"
130131
Name string `json:"name,omitempty"`
131132
// Flag that indicates whether the deployment should be paused.
132133
Paused *bool `json:"paused,omitempty"`

api/v1/atlasdeployment_types_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright 2025 MongoDB Inc
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package v1
16+
17+
import (
18+
"testing"
19+
20+
"github.com/stretchr/testify/assert"
21+
"github.com/stretchr/testify/require"
22+
"k8s.io/apimachinery/pkg/runtime"
23+
24+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1/common"
25+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/cel"
26+
)
27+
28+
func TestDeploymentCELChecks(t *testing.T) {
29+
for _, tc := range []struct {
30+
title string
31+
old, obj *AtlasDeployment
32+
expectedErrors []string
33+
}{
34+
{
35+
title: "Cannot rename a deployment",
36+
old: &AtlasDeployment{
37+
Spec: AtlasDeploymentSpec{
38+
DeploymentSpec: &AdvancedDeploymentSpec{
39+
Name: "name-old",
40+
},
41+
},
42+
},
43+
obj: &AtlasDeployment{
44+
Spec: AtlasDeploymentSpec{
45+
DeploymentSpec: &AdvancedDeploymentSpec{
46+
Name: "name-new",
47+
},
48+
},
49+
},
50+
expectedErrors: []string{"spec.deploymentSpec.name: Invalid value: \"string\": Name cannot be modified after deployment creation"},
51+
},
52+
} {
53+
t.Run(tc.title, func(t *testing.T) {
54+
// inject a project to avoid other CEL validations being hit
55+
tc.obj.Spec.ProjectRef = &common.ResourceRefNamespaced{Name: "some-project"}
56+
unstructuredOldObject, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&tc.old)
57+
require.NoError(t, err)
58+
unstructuredObject, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&tc.obj)
59+
require.NoError(t, err)
60+
61+
crdPath := "../../config/crd/bases/atlas.mongodb.com_atlasdeployments.yaml"
62+
validator, err := cel.VersionValidatorFromFile(t, crdPath, "v1")
63+
assert.NoError(t, err)
64+
errs := validator(unstructuredObject, unstructuredOldObject)
65+
66+
require.Equal(t, tc.expectedErrors, cel.ErrorListAsStrings(errs))
67+
})
68+
}
69+
}

api/v1/atlasproject_types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ func init() {
4343
type AtlasProjectSpec struct {
4444

4545
// Name is the name of the Project that is created in Atlas by the Operator if it doesn't exist yet.
46+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Name cannot be modified after project creation"
4647
Name string `json:"name"`
4748

4849
// RegionUsageRestrictions designate the project's AWS region when using Atlas for Government.

api/v1/atlasproject_types_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,15 @@ import (
2121
"time"
2222

2323
"github.com/google/go-cmp/cmp"
24+
"github.com/stretchr/testify/assert"
25+
"github.com/stretchr/testify/require"
2426
"go.mongodb.org/atlas-sdk/v20250312002/admin"
27+
"k8s.io/apimachinery/pkg/runtime"
2528
"sigs.k8s.io/yaml"
2629

2730
"github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1/common"
2831
internalcmp "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/cmp"
32+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/cel"
2933
)
3034

3135
func TestSpecEquality(t *testing.T) {
@@ -138,3 +142,40 @@ func mustMarshal(t *testing.T, what any) string {
138142
}
139143
return string(result)
140144
}
145+
146+
func TestProjectCELChecks(t *testing.T) {
147+
for _, tc := range []struct {
148+
title string
149+
old, obj *AtlasProject
150+
expectedErrors []string
151+
}{
152+
{
153+
title: "Cannot rename a project",
154+
old: &AtlasProject{
155+
Spec: AtlasProjectSpec{
156+
Name: "name-old",
157+
},
158+
},
159+
obj: &AtlasProject{
160+
Spec: AtlasProjectSpec{
161+
Name: "name-new",
162+
},
163+
},
164+
expectedErrors: []string{"spec.name: Invalid value: \"string\": Name cannot be modified after project creation"},
165+
},
166+
} {
167+
t.Run(tc.title, func(t *testing.T) {
168+
unstructuredOldObject, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&tc.old)
169+
require.NoError(t, err)
170+
unstructuredObject, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&tc.obj)
171+
require.NoError(t, err)
172+
173+
crdPath := "../../config/crd/bases/atlas.mongodb.com_atlasprojects.yaml"
174+
validator, err := cel.VersionValidatorFromFile(t, crdPath, "v1")
175+
assert.NoError(t, err)
176+
errs := validator(unstructuredObject, unstructuredOldObject)
177+
178+
require.Equal(t, tc.expectedErrors, cel.ErrorListAsStrings(errs))
179+
})
180+
}
181+
}

config/crd/bases/atlas.mongodb.com_atlasdeployments.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,9 @@ spec:
197197
Can only contain ASCII letters, numbers, and hyphens.
198198
pattern: ^[a-zA-Z0-9][a-zA-Z0-9-]*$
199199
type: string
200+
x-kubernetes-validations:
201+
- message: Name cannot be modified after deployment creation
202+
rule: self == oldSelf
200203
paused:
201204
description: Flag that indicates whether the deployment should
202205
be paused.

config/crd/bases/atlas.mongodb.com_atlasprojects.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,9 @@ spec:
746746
description: Name is the name of the Project that is created in Atlas
747747
by the Operator if it doesn't exist yet.
748748
type: string
749+
x-kubernetes-validations:
750+
- message: Name cannot be modified after project creation
751+
rule: self == oldSelf
749752
networkPeers:
750753
description: NetworkPeers is a list of Network Peers configured for
751754
the current Project.

0 commit comments

Comments
 (0)