diff --git a/api/v1/atlasdeployment_types.go b/api/v1/atlasdeployment_types.go index ff0a43ccd5..753faeae54 100644 --- a/api/v1/atlasdeployment_types.go +++ b/api/v1/atlasdeployment_types.go @@ -127,6 +127,7 @@ type AdvancedDeploymentSpec struct { // Can only contain ASCII letters, numbers, and hyphens. // +kubebuilder:validation:Required // +kubebuilder:validation:Pattern:=^[a-zA-Z0-9][a-zA-Z0-9-]*$ + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Name cannot be modified after deployment creation" Name string `json:"name,omitempty"` // Flag that indicates whether the deployment should be paused. Paused *bool `json:"paused,omitempty"` diff --git a/api/v1/atlasdeployment_types_test.go b/api/v1/atlasdeployment_types_test.go new file mode 100644 index 0000000000..af2576974e --- /dev/null +++ b/api/v1/atlasdeployment_types_test.go @@ -0,0 +1,69 @@ +// Copyright 2025 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 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/test/helper/cel" +) + +func TestDeploymentCELChecks(t *testing.T) { + for _, tc := range []struct { + title string + old, obj *AtlasDeployment + expectedErrors []string + }{ + { + title: "Cannot rename a deployment", + old: &AtlasDeployment{ + Spec: AtlasDeploymentSpec{ + DeploymentSpec: &AdvancedDeploymentSpec{ + Name: "name-old", + }, + }, + }, + obj: &AtlasDeployment{ + Spec: AtlasDeploymentSpec{ + DeploymentSpec: &AdvancedDeploymentSpec{ + Name: "name-new", + }, + }, + }, + expectedErrors: []string{"spec.deploymentSpec.name: Invalid value: \"string\": Name cannot be modified after deployment creation"}, + }, + } { + 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_atlasdeployments.yaml" + validator, err := cel.VersionValidatorFromFile(t, crdPath, "v1") + assert.NoError(t, err) + errs := validator(unstructuredObject, unstructuredOldObject) + + require.Equal(t, tc.expectedErrors, cel.ErrorListAsStrings(errs)) + }) + } +} diff --git a/api/v1/atlasproject_types.go b/api/v1/atlasproject_types.go index 64461d9515..abd6688ce1 100644 --- a/api/v1/atlasproject_types.go +++ b/api/v1/atlasproject_types.go @@ -43,6 +43,7 @@ func init() { type AtlasProjectSpec struct { // Name is the name of the Project that is created in Atlas by the Operator if it doesn't exist yet. + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Name cannot be modified after project creation" Name string `json:"name"` // RegionUsageRestrictions designate the project's AWS region when using Atlas for Government. diff --git a/api/v1/atlasproject_types_test.go b/api/v1/atlasproject_types_test.go index 939b7d8150..36785a805b 100644 --- a/api/v1/atlasproject_types_test.go +++ b/api/v1/atlasproject_types_test.go @@ -21,11 +21,15 @@ import ( "time" "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "go.mongodb.org/atlas-sdk/v20250312002/admin" + "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/yaml" "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1/common" internalcmp "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/cmp" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/cel" ) func TestSpecEquality(t *testing.T) { @@ -138,3 +142,40 @@ func mustMarshal(t *testing.T, what any) string { } return string(result) } + +func TestProjectCELChecks(t *testing.T) { + for _, tc := range []struct { + title string + old, obj *AtlasProject + expectedErrors []string + }{ + { + title: "Cannot rename a project", + old: &AtlasProject{ + Spec: AtlasProjectSpec{ + Name: "name-old", + }, + }, + obj: &AtlasProject{ + Spec: AtlasProjectSpec{ + Name: "name-new", + }, + }, + expectedErrors: []string{"spec.name: Invalid value: \"string\": Name cannot be modified after project creation"}, + }, + } { + t.Run(tc.title, func(t *testing.T) { + 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_atlasprojects.yaml" + validator, err := cel.VersionValidatorFromFile(t, crdPath, "v1") + assert.NoError(t, err) + errs := validator(unstructuredObject, unstructuredOldObject) + + require.Equal(t, tc.expectedErrors, cel.ErrorListAsStrings(errs)) + }) + } +} diff --git a/config/crd/bases/atlas.mongodb.com_atlasdeployments.yaml b/config/crd/bases/atlas.mongodb.com_atlasdeployments.yaml index d56acb0ebc..0ffa587a30 100644 --- a/config/crd/bases/atlas.mongodb.com_atlasdeployments.yaml +++ b/config/crd/bases/atlas.mongodb.com_atlasdeployments.yaml @@ -197,6 +197,9 @@ spec: Can only contain ASCII letters, numbers, and hyphens. pattern: ^[a-zA-Z0-9][a-zA-Z0-9-]*$ type: string + x-kubernetes-validations: + - message: Name cannot be modified after deployment creation + rule: self == oldSelf paused: description: Flag that indicates whether the deployment should be paused. diff --git a/config/crd/bases/atlas.mongodb.com_atlasprojects.yaml b/config/crd/bases/atlas.mongodb.com_atlasprojects.yaml index 18164617ea..1e4d196b64 100644 --- a/config/crd/bases/atlas.mongodb.com_atlasprojects.yaml +++ b/config/crd/bases/atlas.mongodb.com_atlasprojects.yaml @@ -746,6 +746,9 @@ spec: description: Name is the name of the Project that is created in Atlas by the Operator if it doesn't exist yet. type: string + x-kubernetes-validations: + - message: Name cannot be modified after project creation + rule: self == oldSelf networkPeers: description: NetworkPeers is a list of Network Peers configured for the current Project.