From d7da0e4aa289fbe3a7dba76c0e9c2ecfb184876b Mon Sep 17 00:00:00 2001 From: Igor Karpukhin Date: Mon, 4 Aug 2025 11:38:29 +0200 Subject: [PATCH 1/7] Added AtlasOrgSettings feature --- .licenses-gomod.sha256 | 2 +- PROJECT | 8 + api/v1/atlasorgsettings_types.go | 119 +++ api/v1/status/atlasorgsettings.go | 24 + api/v1/status/zz_generated.deepcopy.go | 16 + api/v1/zz_generated.deepcopy.go | 114 +++ .../atlas.mongodb.com_atlasorgsettings.yaml | 192 +++++ .../atlas.mongodb.com_atlasorgsettings.yaml | 182 +++++ config/crd/kustomization.yaml | 1 + config/rbac/atlasorgsettings_editor_role.yaml | 24 + config/rbac/atlasorgsettings_viewer_role.yaml | 20 + config/rbac/clusterwide/role.yaml | 3 + config/rbac/namespaced/role.yaml | 3 + go.mod | 1 + go.sum | 2 + .../atlas.mongodb.com_atlasorgsettings.yaml | 192 +++++ helm-charts/atlas-operator/rbac.yaml | 2 + internal/controller/atlas/provider.go | 23 +- .../atlasorgsettings_controller.go | 112 +++ .../atlasorgsettings_controller_test.go | 304 ++++++++ .../controller/atlasorgsettings/handler.go | 107 +++ .../atlasorgsettings/handler_test.go | 711 ++++++++++++++++++ internal/controller/registry.go | 3 + internal/indexer/atlasorgsettingssecrets.go | 38 + internal/indexer/indexer.go | 1 + .../translation/atlasorgsetting_service.go | 156 ++++ .../atlasorgsettings/atlasorgsettings.go | 87 +++ .../atlasorgsettings/atlasorgsettings_test.go | 644 ++++++++++++++++ .../translation/atlasorgsettings/service.go | 66 ++ .../atlasorgsettings/service_test.go | 387 ++++++++++ licenses.csv | 153 ---- tools/clean/go.mod | 1 + tools/clean/go.sum | 2 + 33 files changed, 3540 insertions(+), 160 deletions(-) create mode 100644 api/v1/atlasorgsettings_types.go create mode 100644 api/v1/status/atlasorgsettings.go create mode 100644 bundle/manifests/atlas.mongodb.com_atlasorgsettings.yaml create mode 100644 config/crd/bases/atlas.mongodb.com_atlasorgsettings.yaml create mode 100644 config/rbac/atlasorgsettings_editor_role.yaml create mode 100644 config/rbac/atlasorgsettings_viewer_role.yaml create mode 100644 helm-charts/atlas-operator-crds/templates/atlas.mongodb.com_atlasorgsettings.yaml create mode 100644 internal/controller/atlasorgsettings/atlasorgsettings_controller.go create mode 100644 internal/controller/atlasorgsettings/atlasorgsettings_controller_test.go create mode 100644 internal/controller/atlasorgsettings/handler.go create mode 100644 internal/controller/atlasorgsettings/handler_test.go create mode 100644 internal/indexer/atlasorgsettingssecrets.go create mode 100644 internal/mocks/translation/atlasorgsetting_service.go create mode 100644 internal/translation/atlasorgsettings/atlasorgsettings.go create mode 100644 internal/translation/atlasorgsettings/atlasorgsettings_test.go create mode 100644 internal/translation/atlasorgsettings/service.go create mode 100644 internal/translation/atlasorgsettings/service_test.go diff --git a/.licenses-gomod.sha256 b/.licenses-gomod.sha256 index adaa9c8bed..860c553f93 100644 --- a/.licenses-gomod.sha256 +++ b/.licenses-gomod.sha256 @@ -1 +1 @@ -100644 83d67cc56911f2a9e16130ab20e4342dd308fca1 go.mod +100644 849a89d4758d68fa1865d1c95a7373fdc76b89fd go.mod diff --git a/PROJECT b/PROJECT index 283a0233d7..d66741b1b4 100644 --- a/PROJECT +++ b/PROJECT @@ -159,4 +159,12 @@ resources: kind: AtlasThirdPartyIntegration path: github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1 version: v1 +- api: + crdVersion: v1 + namespaced: true + domain: mongodb.com + group: atlas + kind: AtlasOrgSettings + path: github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1 + version: v1 version: "3" diff --git a/api/v1/atlasorgsettings_types.go b/api/v1/atlasorgsettings_types.go new file mode 100644 index 0000000000..338df81851 --- /dev/null +++ b/api/v1/atlasorgsettings_types.go @@ -0,0 +1,119 @@ +// 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 ( + 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(&AtlasOrgSettings{}) + SchemeBuilder.Register(&AtlasOrgSettingsList{}) +} + +type AtlasOrgSettingsSpec struct { + // OrgId Unique 24-hexadecimal digit string that identifies the organization that + // contains your projects + // +required + OrgID string `json:"orgID"` + + // ConnectionSecretRef is the name of the Kubernetes Secret which contains the information about the way to connect to + // Atlas (Public & Private API keys). + ConnectionSecretRef *api.LocalObjectReference `json:"connectionSecretRef,omitempty"` + + // ApiAccessListRequired Flag that indicates whether to require API operations to + // originate from an IP Address added to the API access list for the specified + // organization. + // +optional + ApiAccessListRequired *bool `json:"apiAccessListRequired,omitempty"` + + // GenAIFeaturesEnabled Flag that indicates whether this organization has access to + // generative AI features. This setting only applies to Atlas Commercial and is + // enabled by default. Once this setting is turned on, Project Owners may be able + // to enable or disable individual AI features at the project level. + // +optional + GenAIFeaturesEnabled *bool `json:"genAIFeaturesEnabled,omitempty"` + + // MaxServiceAccountSecretValidityInHours Number that represents the maximum period + // before expiry in hours for new Atlas Admin API Service Account secrets within + // the specified organization. + // +optional + MaxServiceAccountSecretValidityInHours *int `json:"maxServiceAccountSecretValidityInHours,omitempty"` + + // MultiFactorAuthRequired Flag that indicates whether to require users to set up + // Multi-Factor Authentication (MFA) before accessing the specified organization. + // To learn more, see: + // https://www.mongodb.com/docs/atlas/security-multi-factor-authentication/. + // +optional + MultiFactorAuthRequired *bool `json:"multiFactorAuthRequired,omitempty"` + + // RestrictEmployeeAccess Flag that indicates whether to block MongoDB Support from + // accessing Atlas infrastructure and cluster logs for any deployment in the + // specified organization without explicit permission. Once this setting is turned + // on, you can grant MongoDB Support a 24-hour bypass access to the Atlas + // deployment to resolve support issues. To learn more, see: + // https://www.mongodb.com/docs/atlas/security-restrict-support-access/. + // +optional + RestrictEmployeeAccess *bool `json:"restrictEmployeeAccess,omitempty"` + + // SecurityContact String that specifies a single email address for the specified + // organization to receive security-related notifications. Specifying a security + // contact does not grant them authorization or access to Atlas for security + // decisions or approvals. An empty string is valid and clears the existing + // security contact (if any). + // +optional + SecurityContact *string `json:"securityContact,omitempty"` + + // StreamsCrossGroupEnabled Flag that indicates whether a group's Atlas Stream + // Processing instances in this organization can create connections to other + // group's clusters in the same organization. + // +optional + StreamsCrossGroupEnabled *bool `json:"streamsCrossGroupEnabled,omitempty"` +} + +// +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:name:plural=AtlasOrgSettings, singular=AtlasOrgSettings +// +kubebuilder:resource:categories=atlas,shortName=aos +// +kubebuilder:subresource:status +type AtlasOrgSettings struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec AtlasOrgSettingsSpec `json:"spec,omitempty"` + Status status.AtlasOrgSettingsStatus `json:"status,omitempty"` +} + +func (aos *AtlasOrgSettings) Credentials() *api.LocalObjectReference { + return aos.Spec.ConnectionSecretRef +} + +func (aos *AtlasOrgSettings) GetConditions() []metav1.Condition { + if aos.Status.Conditions == nil { + return []metav1.Condition{} + } + return aos.Status.Conditions +} + +// +kubebuilder:object:root=true +type AtlasOrgSettingsList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []AtlasOrgSettings `json:"items"` +} diff --git a/api/v1/status/atlasorgsettings.go b/api/v1/status/atlasorgsettings.go new file mode 100644 index 0000000000..ab71faeef4 --- /dev/null +++ b/api/v1/status/atlasorgsettings.go @@ -0,0 +1,24 @@ +// 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 status + +// +k8s:deepcopy-gen=true +type AtlasOrgSettingsStatus struct { + UnifiedStatus `json:",inline"` +} + +// +k8s:deepcopy-gen=false + +type AtlasOrgSettingsStatusOption func(s *AtlasOrgSettingsStatus) diff --git a/api/v1/status/zz_generated.deepcopy.go b/api/v1/status/zz_generated.deepcopy.go index 842fd61213..44b1107985 100644 --- a/api/v1/status/zz_generated.deepcopy.go +++ b/api/v1/status/zz_generated.deepcopy.go @@ -267,6 +267,22 @@ func (in *AtlasNetworkPeeringStatus) DeepCopy() *AtlasNetworkPeeringStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AtlasOrgSettingsStatus) DeepCopyInto(out *AtlasOrgSettingsStatus) { + *out = *in + in.UnifiedStatus.DeepCopyInto(&out.UnifiedStatus) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AtlasOrgSettingsStatus. +func (in *AtlasOrgSettingsStatus) DeepCopy() *AtlasOrgSettingsStatus { + if in == nil { + return nil + } + out := new(AtlasOrgSettingsStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AtlasPrivateEndpointStatus) DeepCopyInto(out *AtlasPrivateEndpointStatus) { *out = *in diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 69180e0152..605ad5fa1f 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -1325,6 +1325,120 @@ func (in *AtlasOnDemandPolicy) DeepCopy() *AtlasOnDemandPolicy { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AtlasOrgSettings) DeepCopyInto(out *AtlasOrgSettings) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AtlasOrgSettings. +func (in *AtlasOrgSettings) DeepCopy() *AtlasOrgSettings { + if in == nil { + return nil + } + out := new(AtlasOrgSettings) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AtlasOrgSettings) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AtlasOrgSettingsList) DeepCopyInto(out *AtlasOrgSettingsList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]AtlasOrgSettings, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AtlasOrgSettingsList. +func (in *AtlasOrgSettingsList) DeepCopy() *AtlasOrgSettingsList { + if in == nil { + return nil + } + out := new(AtlasOrgSettingsList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *AtlasOrgSettingsList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *AtlasOrgSettingsSpec) DeepCopyInto(out *AtlasOrgSettingsSpec) { + *out = *in + if in.ConnectionSecretRef != nil { + in, out := &in.ConnectionSecretRef, &out.ConnectionSecretRef + *out = new(api.LocalObjectReference) + **out = **in + } + if in.ApiAccessListRequired != nil { + in, out := &in.ApiAccessListRequired, &out.ApiAccessListRequired + *out = new(bool) + **out = **in + } + if in.GenAIFeaturesEnabled != nil { + in, out := &in.GenAIFeaturesEnabled, &out.GenAIFeaturesEnabled + *out = new(bool) + **out = **in + } + if in.MaxServiceAccountSecretValidityInHours != nil { + in, out := &in.MaxServiceAccountSecretValidityInHours, &out.MaxServiceAccountSecretValidityInHours + *out = new(int) + **out = **in + } + if in.MultiFactorAuthRequired != nil { + in, out := &in.MultiFactorAuthRequired, &out.MultiFactorAuthRequired + *out = new(bool) + **out = **in + } + if in.RestrictEmployeeAccess != nil { + in, out := &in.RestrictEmployeeAccess, &out.RestrictEmployeeAccess + *out = new(bool) + **out = **in + } + if in.SecurityContact != nil { + in, out := &in.SecurityContact, &out.SecurityContact + *out = new(string) + **out = **in + } + if in.StreamsCrossGroupEnabled != nil { + in, out := &in.StreamsCrossGroupEnabled, &out.StreamsCrossGroupEnabled + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AtlasOrgSettingsSpec. +func (in *AtlasOrgSettingsSpec) DeepCopy() *AtlasOrgSettingsSpec { + if in == nil { + return nil + } + out := new(AtlasOrgSettingsSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AtlasPrivateEndpoint) DeepCopyInto(out *AtlasPrivateEndpoint) { *out = *in diff --git a/bundle/manifests/atlas.mongodb.com_atlasorgsettings.yaml b/bundle/manifests/atlas.mongodb.com_atlasorgsettings.yaml new file mode 100644 index 0000000000..8dcceceb33 --- /dev/null +++ b/bundle/manifests/atlas.mongodb.com_atlasorgsettings.yaml @@ -0,0 +1,192 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.2 + creationTimestamp: null + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: mongodb-atlas-kubernetes-operator + app.kubernetes.io/name: mongodb-atlas-kubernetes-operator + name: atlasorgsettings.atlas.mongodb.com +spec: + group: atlas.mongodb.com + names: + categories: + - atlas + kind: AtlasOrgSettings + listKind: AtlasOrgSettingsList + plural: atlasorgsettings + shortNames: + - aos + singular: atlasorgsettings + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + apiAccessListRequired: + description: |- + ApiAccessListRequired Flag that indicates whether to require API operations to + originate from an IP Address added to the API access list for the specified + organization. + type: boolean + connectionSecretRef: + description: |- + ConnectionSecretRef is the name of the Kubernetes Secret which contains the information about the way to connect to + Atlas (Public & Private API keys). + properties: + name: + description: |- + Name of the resource being referred to + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + genAIFeaturesEnabled: + description: |- + GenAIFeaturesEnabled Flag that indicates whether this organization has access to + generative AI features. This setting only applies to Atlas Commercial and is + enabled by default. Once this setting is turned on, Project Owners may be able + to enable or disable individual AI features at the project level. + type: boolean + maxServiceAccountSecretValidityInHours: + description: |- + MaxServiceAccountSecretValidityInHours Number that represents the maximum period + before expiry in hours for new Atlas Admin API Service Account secrets within + the specified organization. + type: integer + multiFactorAuthRequired: + description: |- + MultiFactorAuthRequired Flag that indicates whether to require users to set up + Multi-Factor Authentication (MFA) before accessing the specified organization. + To learn more, see: + https://www.mongodb.com/docs/atlas/security-multi-factor-authentication/. + type: boolean + orgID: + description: |- + OrgId Unique 24-hexadecimal digit string that identifies the organization that + contains your projects + type: string + restrictEmployeeAccess: + description: |- + RestrictEmployeeAccess Flag that indicates whether to block MongoDB Support from + accessing Atlas infrastructure and cluster logs for any deployment in the + specified organization without explicit permission. Once this setting is turned + on, you can grant MongoDB Support a 24-hour bypass access to the Atlas + deployment to resolve support issues. To learn more, see: + https://www.mongodb.com/docs/atlas/security-restrict-support-access/. + type: boolean + securityContact: + description: |- + SecurityContact String that specifies a single email address for the specified + organization to receive security-related notifications. Specifying a security + contact does not grant them authorization or access to Atlas for security + decisions or approvals. An empty string is valid and clears the existing + security contact (if any). + type: string + streamsCrossGroupEnabled: + description: |- + StreamsCrossGroupEnabled Flag that indicates whether a group's Atlas Stream + Processing instances in this organization can create connections to other + group's clusters in the same organization. + type: boolean + required: + - orgID + type: object + status: + properties: + conditions: + description: Conditions holding the status details + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/config/crd/bases/atlas.mongodb.com_atlasorgsettings.yaml b/config/crd/bases/atlas.mongodb.com_atlasorgsettings.yaml new file mode 100644 index 0000000000..32fe1e09b9 --- /dev/null +++ b/config/crd/bases/atlas.mongodb.com_atlasorgsettings.yaml @@ -0,0 +1,182 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.2 + name: atlasorgsettings.atlas.mongodb.com +spec: + group: atlas.mongodb.com + names: + categories: + - atlas + kind: AtlasOrgSettings + listKind: AtlasOrgSettingsList + plural: atlasorgsettings + shortNames: + - aos + singular: atlasorgsettings + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + apiAccessListRequired: + description: |- + ApiAccessListRequired Flag that indicates whether to require API operations to + originate from an IP Address added to the API access list for the specified + organization. + type: boolean + connectionSecretRef: + description: |- + ConnectionSecretRef is the name of the Kubernetes Secret which contains the information about the way to connect to + Atlas (Public & Private API keys). + properties: + name: + description: |- + Name of the resource being referred to + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + genAIFeaturesEnabled: + description: |- + GenAIFeaturesEnabled Flag that indicates whether this organization has access to + generative AI features. This setting only applies to Atlas Commercial and is + enabled by default. Once this setting is turned on, Project Owners may be able + to enable or disable individual AI features at the project level. + type: boolean + maxServiceAccountSecretValidityInHours: + description: |- + MaxServiceAccountSecretValidityInHours Number that represents the maximum period + before expiry in hours for new Atlas Admin API Service Account secrets within + the specified organization. + type: integer + multiFactorAuthRequired: + description: |- + MultiFactorAuthRequired Flag that indicates whether to require users to set up + Multi-Factor Authentication (MFA) before accessing the specified organization. + To learn more, see: + https://www.mongodb.com/docs/atlas/security-multi-factor-authentication/. + type: boolean + orgID: + description: |- + OrgId Unique 24-hexadecimal digit string that identifies the organization that + contains your projects + type: string + restrictEmployeeAccess: + description: |- + RestrictEmployeeAccess Flag that indicates whether to block MongoDB Support from + accessing Atlas infrastructure and cluster logs for any deployment in the + specified organization without explicit permission. Once this setting is turned + on, you can grant MongoDB Support a 24-hour bypass access to the Atlas + deployment to resolve support issues. To learn more, see: + https://www.mongodb.com/docs/atlas/security-restrict-support-access/. + type: boolean + securityContact: + description: |- + SecurityContact String that specifies a single email address for the specified + organization to receive security-related notifications. Specifying a security + contact does not grant them authorization or access to Atlas for security + decisions or approvals. An empty string is valid and clears the existing + security contact (if any). + type: string + streamsCrossGroupEnabled: + description: |- + StreamsCrossGroupEnabled Flag that indicates whether a group's Atlas Stream + Processing instances in this organization can create connections to other + group's clusters in the same organization. + type: boolean + required: + - orgID + type: object + status: + properties: + conditions: + description: Conditions holding the status details + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 460174d646..7725db8c9b 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -20,5 +20,6 @@ resources: - bases/atlas.mongodb.com_atlasnetworkcontainers.yaml - bases/atlas.mongodb.com_atlasnetworkpeerings.yaml - bases/atlas.mongodb.com_atlasthirdpartyintegrations.yaml + - bases/atlas.mongodb.com_atlasorgsettings.yaml configurations: - kustomizeconfig.yaml diff --git a/config/rbac/atlasorgsettings_editor_role.yaml b/config/rbac/atlasorgsettings_editor_role.yaml new file mode 100644 index 0000000000..e45bf93583 --- /dev/null +++ b/config/rbac/atlasorgsettings_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit atlasorgsettings. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: atlasorgsettings-editor-role +rules: + - apiGroups: + - atlas.mongodb.com + resources: + - atlasorgsettings + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - atlas.mongodb.com + resources: + - atlasorgsettings/status + verbs: + - get diff --git a/config/rbac/atlasorgsettings_viewer_role.yaml b/config/rbac/atlasorgsettings_viewer_role.yaml new file mode 100644 index 0000000000..345dba41a4 --- /dev/null +++ b/config/rbac/atlasorgsettings_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view atlasprivateendpoints. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: atlasorgsettings-viewer-role +rules: + - apiGroups: + - atlas.mongodb.com + resources: + - atlasorgsettings + verbs: + - get + - list + - watch + - apiGroups: + - atlas.mongodb.com + resources: + - atlasorgsettings/status + verbs: + - get diff --git a/config/rbac/clusterwide/role.yaml b/config/rbac/clusterwide/role.yaml index db2ac57eca..cc196fdccf 100644 --- a/config/rbac/clusterwide/role.yaml +++ b/config/rbac/clusterwide/role.yaml @@ -37,6 +37,7 @@ rules: - atlasipaccesslists - atlasnetworkcontainers - atlasnetworkpeerings + - atlasorgsettings - atlasprivateendpoints - atlasprojects - atlassearchindexconfigs @@ -66,6 +67,7 @@ rules: - atlasipaccesslists/status - atlasnetworkcontainers/status - atlasnetworkpeerings/status + - atlasorgsettings/status - atlasprivateendpoints/status - atlasprojects/status - atlassearchindexconfigs/status @@ -83,6 +85,7 @@ rules: - atlasipaccesslists/finalizers - atlasnetworkcontainers/finalizers - atlasnetworkpeerings/finalizers + - atlasorgsettings/finalizers - atlasthirdpartyintegrations/finalizers verbs: - update diff --git a/config/rbac/namespaced/role.yaml b/config/rbac/namespaced/role.yaml index 1a471e20f4..19969c3867 100644 --- a/config/rbac/namespaced/role.yaml +++ b/config/rbac/namespaced/role.yaml @@ -37,6 +37,7 @@ rules: - atlasfederatedauths - atlasipaccesslists - atlasnetworkpeerings + - atlasorgsettings - atlasprivateendpoints - atlasprojects - atlassearchindexconfigs @@ -64,6 +65,7 @@ rules: - atlasfederatedauths/status - atlasipaccesslists/status - atlasnetworkpeerings/status + - atlasorgsettings/status - atlasprivateendpoints/status - atlasprojects/status - atlassearchindexconfigs/status @@ -80,6 +82,7 @@ rules: resources: - atlasipaccesslists/finalizers - atlasnetworkpeerings/finalizers + - atlasorgsettings/finalizers - atlasthirdpartyintegrations/finalizers verbs: - update diff --git a/go.mod b/go.mod index 83d67cc569..2691da691d 100644 --- a/go.mod +++ b/go.mod @@ -28,6 +28,7 @@ require ( github.com/stretchr/testify v1.10.0 github.com/yudai/gojsondiff v1.0.0 go.mongodb.org/atlas-sdk/v20250312002 v20250312002.0.0 + go.mongodb.org/atlas-sdk/v20250312006 v20250312006.0.0 go.mongodb.org/mongo-driver v1.17.4 go.uber.org/zap v1.27.0 golang.org/x/sync v0.16.0 diff --git a/go.sum b/go.sum index bd1a84dcc3..9b9d10857f 100644 --- a/go.sum +++ b/go.sum @@ -291,6 +291,8 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/atlas-sdk/v20250312002 v20250312002.0.0 h1:KX8PrYp3/PCSxG4NbGLcc3+EsNcfyhcvylGbe/oRlx8= go.mongodb.org/atlas-sdk/v20250312002 v20250312002.0.0/go.mod h1:HHCmHxHPdJRr1bUXlvRIZbm7M4gRujjur1GnjE44YgA= +go.mongodb.org/atlas-sdk/v20250312006 v20250312006.0.0 h1:3y9pfi0UYVTz/44lhtVQkvDWg/QMB+jOoxA7poab1nU= +go.mongodb.org/atlas-sdk/v20250312006 v20250312006.0.0/go.mod h1:UZYSaCimjGs3j+wMwgHSKUSIvoJXzmy/xrer0t5TLgo= go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw= go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= diff --git a/helm-charts/atlas-operator-crds/templates/atlas.mongodb.com_atlasorgsettings.yaml b/helm-charts/atlas-operator-crds/templates/atlas.mongodb.com_atlasorgsettings.yaml new file mode 100644 index 0000000000..8dcceceb33 --- /dev/null +++ b/helm-charts/atlas-operator-crds/templates/atlas.mongodb.com_atlasorgsettings.yaml @@ -0,0 +1,192 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.17.2 + creationTimestamp: null + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/instance: mongodb-atlas-kubernetes-operator + app.kubernetes.io/name: mongodb-atlas-kubernetes-operator + name: atlasorgsettings.atlas.mongodb.com +spec: + group: atlas.mongodb.com + names: + categories: + - atlas + kind: AtlasOrgSettings + listKind: AtlasOrgSettingsList + plural: atlasorgsettings + shortNames: + - aos + singular: atlasorgsettings + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Ready")].status + name: Ready + type: string + name: v1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + apiAccessListRequired: + description: |- + ApiAccessListRequired Flag that indicates whether to require API operations to + originate from an IP Address added to the API access list for the specified + organization. + type: boolean + connectionSecretRef: + description: |- + ConnectionSecretRef is the name of the Kubernetes Secret which contains the information about the way to connect to + Atlas (Public & Private API keys). + properties: + name: + description: |- + Name of the resource being referred to + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + genAIFeaturesEnabled: + description: |- + GenAIFeaturesEnabled Flag that indicates whether this organization has access to + generative AI features. This setting only applies to Atlas Commercial and is + enabled by default. Once this setting is turned on, Project Owners may be able + to enable or disable individual AI features at the project level. + type: boolean + maxServiceAccountSecretValidityInHours: + description: |- + MaxServiceAccountSecretValidityInHours Number that represents the maximum period + before expiry in hours for new Atlas Admin API Service Account secrets within + the specified organization. + type: integer + multiFactorAuthRequired: + description: |- + MultiFactorAuthRequired Flag that indicates whether to require users to set up + Multi-Factor Authentication (MFA) before accessing the specified organization. + To learn more, see: + https://www.mongodb.com/docs/atlas/security-multi-factor-authentication/. + type: boolean + orgID: + description: |- + OrgId Unique 24-hexadecimal digit string that identifies the organization that + contains your projects + type: string + restrictEmployeeAccess: + description: |- + RestrictEmployeeAccess Flag that indicates whether to block MongoDB Support from + accessing Atlas infrastructure and cluster logs for any deployment in the + specified organization without explicit permission. Once this setting is turned + on, you can grant MongoDB Support a 24-hour bypass access to the Atlas + deployment to resolve support issues. To learn more, see: + https://www.mongodb.com/docs/atlas/security-restrict-support-access/. + type: boolean + securityContact: + description: |- + SecurityContact String that specifies a single email address for the specified + organization to receive security-related notifications. Specifying a security + contact does not grant them authorization or access to Atlas for security + decisions or approvals. An empty string is valid and clears the existing + security contact (if any). + type: string + streamsCrossGroupEnabled: + description: |- + StreamsCrossGroupEnabled Flag that indicates whether a group's Atlas Stream + Processing instances in this organization can create connections to other + group's clusters in the same organization. + type: boolean + required: + - orgID + type: object + status: + properties: + conditions: + description: Conditions holding the status details + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/helm-charts/atlas-operator/rbac.yaml b/helm-charts/atlas-operator/rbac.yaml index 6deda9b3a0..f9e5bb41db 100644 --- a/helm-charts/atlas-operator/rbac.yaml +++ b/helm-charts/atlas-operator/rbac.yaml @@ -38,6 +38,7 @@ - atlasstreaminstances - atlasteams - atlasthirdpartyintegrations + - atlasorgsettings verbs: - create - delete @@ -67,6 +68,7 @@ - atlasstreaminstances/status - atlasteams/status - atlasthirdpartyintegrations/status + - atlasorgsettings/status verbs: - get - patch diff --git a/internal/controller/atlas/provider.go b/internal/controller/atlas/provider.go index ea6674b336..0297155db9 100644 --- a/internal/controller/atlas/provider.go +++ b/internal/controller/atlas/provider.go @@ -23,7 +23,8 @@ import ( "strings" "github.com/mongodb-forks/digest" - "go.mongodb.org/atlas-sdk/v20250312002/admin" + v20250312002 "go.mongodb.org/atlas-sdk/v20250312002/admin" + v20250312006 "go.mongodb.org/atlas-sdk/v20250312006/admin" "go.uber.org/zap" "github.com/mongodb/mongodb-atlas-kubernetes/v2/api" @@ -44,7 +45,8 @@ type Provider interface { } type ClientSet struct { - SdkClient20250312002 *admin.APIClient + SdkClient20250312002 *v20250312002.APIClient + SdkClient20250312006 *v20250312006.APIClient } type ProductionProvider struct { @@ -131,16 +133,25 @@ func (p *ProductionProvider) SdkClientSet(ctx context.Context, creds *Credential httpClient := &http.Client{Transport: transport} - clientv20250312002, err := admin.NewClient( - admin.UseBaseURL(p.domain), - admin.UseHTTPClient(httpClient), - admin.UseUserAgent(operatorUserAgent())) + clientv20250312002, err := v20250312002.NewClient( + v20250312002.UseBaseURL(p.domain), + v20250312002.UseHTTPClient(httpClient), + v20250312002.UseUserAgent(operatorUserAgent())) + if err != nil { + return nil, err + } + + clientv20250312006, err := v20250312006.NewClient( + v20250312006.UseBaseURL(p.domain), + v20250312006.UseHTTPClient(httpClient), + v20250312006.UseUserAgent(operatorUserAgent())) if err != nil { return nil, err } return &ClientSet{ SdkClient20250312002: clientv20250312002, + SdkClient20250312006: clientv20250312006, }, nil } diff --git a/internal/controller/atlasorgsettings/atlasorgsettings_controller.go b/internal/controller/atlasorgsettings/atlasorgsettings_controller.go new file mode 100644 index 0000000000..73241ecf3e --- /dev/null +++ b/internal/controller/atlasorgsettings/atlasorgsettings_controller.go @@ -0,0 +1,112 @@ +// 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 atlasorgsettings + +import ( + "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + controllerruntime "sigs.k8s.io/controller-runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" + ctrlrtbuilder "sigs.k8s.io/controller-runtime/pkg/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/cluster" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/controller/atlas" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/controller/reconciler" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/indexer" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/atlasorgsettings" + ctrlstate "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/state" + mckpredicate "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/predicate" +) + +// +kubebuilder:rbac:groups=atlas.mongodb.com,resources=atlasorgsettings,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=atlas.mongodb.com,resources=atlasorgsettings/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=atlas.mongodb.com,resources=atlasorgsettings/finalizers,verbs=update +// +kubebuilder:rbac:groups=atlas.mongodb.com,namespace=default,resources=atlasorgsettings,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=atlas.mongodb.com,namespace=default,resources=atlasorgsettings/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=atlas.mongodb.com,namespace=default,resources=atlasorgsettings/finalizers,verbs=update + +type serviceBuilderFunc func(*atlas.ClientSet) atlasorgsettings.AtlasOrgSettingsService + +type AtlasOrgSettingsHandler struct { + ctrlstate.StateHandler[akov2.AtlasOrgSettings] + reconciler.AtlasReconciler + deletionProtection bool + serviceBuilder serviceBuilderFunc +} + +func NewAtlasOrgSettingsReconciler( + c cluster.Cluster, + atlasProvider atlas.Provider, + logger *zap.Logger, + globalSecretRef client.ObjectKey, + deletionProtection bool, + reapplySupport bool, +) *ctrlstate.Reconciler[akov2.AtlasOrgSettings] { + orgSettingsHandler := &AtlasOrgSettingsHandler{ + AtlasReconciler: reconciler.AtlasReconciler{ + Client: c.GetClient(), + AtlasProvider: atlasProvider, + Log: logger.Named("controllers").Named("AtlasOrgSettings").Sugar(), + GlobalSecretRef: globalSecretRef, + }, + deletionProtection: deletionProtection, + serviceBuilder: func(clientSet *atlas.ClientSet) atlasorgsettings.AtlasOrgSettingsService { + return atlasorgsettings.NewAtlasOrgSettingsService(clientSet.SdkClient20250312006.OrganizationsApi) + }, + } + return ctrlstate.NewStateReconciler( + orgSettingsHandler, + ctrlstate.WithCluster[akov2.AtlasOrgSettings](c), + ctrlstate.WithReapplySupport[akov2.AtlasOrgSettings](reapplySupport), + ) +} + +func (h *AtlasOrgSettingsHandler) For() (client.Object, builder.Predicates) { + obj := &akov2.AtlasOrgSettings{} + return obj, ctrlrtbuilder.WithPredicates( + predicate.Or( + mckpredicate.AnnotationChanged("mongodb.com/reapply-period"), + predicate.GenerationChangedPredicate{}, + ), + mckpredicate.IgnoreDeletedPredicate[client.Object](), + ) +} + +func (h *AtlasOrgSettingsHandler) SetupWithManager(mgr ctrl.Manager, rec reconcile.Reconciler, defaultOptions controller.Options) error { + h.Client = mgr.GetClient() + return controllerruntime.NewControllerManagedBy(mgr). + Named("AtlasOrgSettings"). + For(h.For()). + Watches( + &corev1.Secret{}, + handler.EnqueueRequestsFromMapFunc(h.findSecretsForOrgSettings()), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). + WithOptions(defaultOptions).Complete(rec) +} + +func (h *AtlasOrgSettingsHandler) findSecretsForOrgSettings() handler.MapFunc { + return indexer.CredentialsIndexMapperFunc( + indexer.AtlasOrgSettingsBySecretsIndex, + func() *akov2.AtlasOrgSettingsList { return &akov2.AtlasOrgSettingsList{} }, + indexer.AtlasOrgSettingsRequest, h.Client, h.Log) +} diff --git a/internal/controller/atlasorgsettings/atlasorgsettings_controller_test.go b/internal/controller/atlasorgsettings/atlasorgsettings_controller_test.go new file mode 100644 index 0000000000..213b88c04d --- /dev/null +++ b/internal/controller/atlasorgsettings/atlasorgsettings_controller_test.go @@ -0,0 +1,304 @@ +// 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 atlasorgsettings + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" + "go.uber.org/zap/zaptest" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/rest" + controllerruntime "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/cluster" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "github.com/mongodb/mongodb-atlas-kubernetes/v2/api" + akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/controller/atlas" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/controller/reconciler" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/indexer" + atlasmock "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/mocks/atlas" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/pointer" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/atlasorgsettings" +) + +var testAtlasSecret = corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-atlas-secret", + Namespace: "default", + }, + Data: map[string][]byte{ + "orgId": []byte("test-org-id"), + "publicApiKey": []byte("test-public-key"), + "privateApiKey": []byte("test-private-key"), + }, +} + +func TestNewAtlasOrgSettingsReconciler(t *testing.T) { + fakeCluster := &fakeCluster{} + atlasProvider := &atlasmock.TestProvider{} + logger := zaptest.NewLogger(t) + globalSecretRef := types.NamespacedName{Name: "global-secret", Namespace: "default"} + + rec := NewAtlasOrgSettingsReconciler( + fakeCluster, + atlasProvider, + logger, + client.ObjectKey{Name: globalSecretRef.Name, Namespace: globalSecretRef.Namespace}, + true, + false, + ) + + assert.NotNil(t, rec) +} + +func TestAtlasOrgSettingsHandlerFor(t *testing.T) { + handler := &AtlasOrgSettingsHandler{} + obj, preds := handler.For() + assert.IsType(t, &akov2.AtlasOrgSettings{}, obj) + assert.NotNil(t, preds) +} + +func TestSetupWithManager(t *testing.T) { + scheme := runtime.NewScheme() + require.NoError(t, akov2.AddToScheme(scheme)) + require.NoError(t, corev1.AddToScheme(scheme)) + + mgr, err := manager.New(&rest.Config{}, manager.Options{Scheme: scheme}) + require.NoError(t, err) + fakeMgr := &fakeManager{ + Manager: mgr, + client: fake.NewClientBuilder().WithScheme(scheme).Build(), + } + + handler := &AtlasOrgSettingsHandler{ + AtlasReconciler: reconciler.AtlasReconciler{ + AtlasProvider: nil, + Client: fakeMgr.GetClient(), + Log: &zap.SugaredLogger{}, + GlobalSecretRef: client.ObjectKey{}, + }, + deletionProtection: false, + serviceBuilder: func(clientSet *atlas.ClientSet) atlasorgsettings.AtlasOrgSettingsService { + return nil + }, + } + + require.NoError(t, handler.SetupWithManager(fakeMgr, &fakeReconciler{}, controller.Options{})) +} + +func TestFindSecretsForOrgSettings(t *testing.T) { + scheme := runtime.NewScheme() + require.NoError(t, corev1.AddToScheme(scheme)) + require.NoError(t, akov2.AddToScheme(scheme)) + logger := zaptest.NewLogger(t) + ctx := context.Background() + + testCases := []struct { + name string + objects []client.Object + want []reconcile.Request + }{ + { + name: "nil on non-secret object", + objects: []client.Object{&corev1.ConfigMap{}}, + want: nil, + }, + { + name: "empty on non-linked secret", + objects: []client.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-secret", + Namespace: "default", + }, + }, + }, + want: []reconcile.Request{}, + }, + { + name: "hit on linked credential secret", + objects: []client.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-credential", + Namespace: "default", + }, + }, + &akov2.AtlasOrgSettings{ + ObjectMeta: metav1.ObjectMeta{ + Name: "org-settings", + Namespace: "default", + }, + Spec: akov2.AtlasOrgSettingsSpec{ + OrgID: "test-org-id", + ConnectionSecretRef: &api.LocalObjectReference{ + Name: "test-credential", + }, + }, + }, + }, + want: []reconcile.Request{ + {NamespacedName: types.NamespacedName{Name: "org-settings", Namespace: "default"}}, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + credIndex := indexer.NewAtlasOrgSettingsByConnectionSecretIndexer(logger) + + fakeClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(tc.objects...). + WithIndex(&akov2.AtlasOrgSettings{}, indexer.AtlasOrgSettingsBySecretsIndex, credIndex.Keys). + Build() + + handler := &AtlasOrgSettingsHandler{ + AtlasReconciler: reconciler.AtlasReconciler{ + Client: fakeClient, + Log: logger.Sugar(), + }, + } + mapper := handler.findSecretsForOrgSettings() + + got := mapper(ctx, tc.objects[0]) + + assert.Equal(t, tc.want, got) + }) + } +} + +func TestAtlasOrgSettingsHandler_NewReconcileContext(t *testing.T) { + scheme := runtime.NewScheme() + require.NoError(t, corev1.AddToScheme(scheme)) + require.NoError(t, akov2.AddToScheme(scheme)) + + orgSettings := &akov2.AtlasOrgSettings{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-org-settings", + Namespace: "default", + }, + Spec: akov2.AtlasOrgSettingsSpec{ + OrgID: "test-org-id", + ConnectionSecretRef: &api.LocalObjectReference{ + Name: "test-atlas-secret", + }, + ApiAccessListRequired: pointer.MakePtr(true), + GenAIFeaturesEnabled: pointer.MakePtr(false), + MultiFactorAuthRequired: pointer.MakePtr(true), + }, + } + + k8sClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(&testAtlasSecret, orgSettings).Build() + + handler := &AtlasOrgSettingsHandler{ + AtlasReconciler: reconciler.AtlasReconciler{ + Client: k8sClient, + AtlasProvider: &atlasmock.TestProvider{ + SdkClientSetFunc: func(ctx context.Context, creds *atlas.Credentials, log *zap.SugaredLogger) (*atlas.ClientSet, error) { + return &atlas.ClientSet{}, nil + }, + }, + }, + deletionProtection: false, + serviceBuilder: func(clientSet *atlas.ClientSet) atlasorgsettings.AtlasOrgSettingsService { + return nil + }, + } + + ctx := context.Background() + req, err := handler.newReconcileContext(ctx, orgSettings) + + require.NoError(t, err) + assert.NotNil(t, req) +} + +func TestAtlasOrgSettingsHandler_ServiceBuilder(t *testing.T) { + handler := &AtlasOrgSettingsHandler{ + serviceBuilder: func(clientSet *atlas.ClientSet) atlasorgsettings.AtlasOrgSettingsService { + return nil + }, + } + + clientSet := &atlas.ClientSet{} + service := handler.serviceBuilder(clientSet) + + assert.Nil(t, service) +} + +func TestAtlasOrgSettingsHandler_DeletionProtection(t *testing.T) { + testCases := []struct { + name string + deletionProtection bool + }{ + { + name: "deletion protection enabled", + deletionProtection: true, + }, + { + name: "deletion protection disabled", + deletionProtection: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + handler := &AtlasOrgSettingsHandler{ + deletionProtection: tc.deletionProtection, + } + + assert.Equal(t, tc.deletionProtection, handler.deletionProtection) + }) + } +} + +type fakeManager struct { + controllerruntime.Manager + client client.Client +} + +func (f *fakeManager) GetClient() client.Client { + return f.client +} + +type fakeCluster struct { + cluster.Cluster +} + +func (m *fakeCluster) GetClient() client.Client { + return &fakeClient{} +} + +type fakeClient struct { + client.Client +} + +type fakeReconciler struct { + reconcile.Reconciler +} diff --git a/internal/controller/atlasorgsettings/handler.go b/internal/controller/atlasorgsettings/handler.go new file mode 100644 index 0000000000..89e43a2b55 --- /dev/null +++ b/internal/controller/atlasorgsettings/handler.go @@ -0,0 +1,107 @@ +// 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 atlasorgsettings + +import ( + "context" + "fmt" + + "sigs.k8s.io/controller-runtime/pkg/client" + + akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/controller/reconciler" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/atlasorgsettings" + ctrlstate "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/state" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/result" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/state" +) + +type reconcileRequest struct { + svc atlasorgsettings.AtlasOrgSettingsService + aos *akov2.AtlasOrgSettings +} + +func (h *AtlasOrgSettingsHandler) newReconcileContext(ctx context.Context, aos *akov2.AtlasOrgSettings) (*reconcileRequest, error) { + var objKey *client.ObjectKey + if aos.Spec.ConnectionSecretRef != nil && aos.Spec.ConnectionSecretRef.Name != "" { + objKey = &client.ObjectKey{ + Namespace: aos.GetNamespace(), + Name: aos.Spec.ConnectionSecretRef.Name, + } + } + + cfg, err := reconciler.GetConnectionConfig(ctx, h.Client, objKey, &h.GlobalSecretRef) + if err != nil { + return nil, err + } + + atlasSdk, err := h.AtlasProvider.SdkClientSet(ctx, cfg.Credentials, h.Log) + if err != nil { + return nil, err + } + return &reconcileRequest{ + svc: h.serviceBuilder(atlasSdk), + aos: aos, + }, nil +} + +func (h *AtlasOrgSettingsHandler) upsert(ctx context.Context, currentState, nextState state.ResourceState, + aos *akov2.AtlasOrgSettings) (ctrlstate.Result, error) { + reconcileCtx, err := h.newReconcileContext(ctx, aos) + if err != nil { + return result.Error(currentState, fmt.Errorf("failed to create reconcile context: %w", err)) + } + + currentAtlasSettings, err := reconcileCtx.svc.Get(ctx, aos.Spec.OrgID) + if err != nil { + return result.Error(currentState, fmt.Errorf("failed to get current org settings from Atlas: %w", err)) + } + + desiredSettings := atlasorgsettings.NewFromAKO(aos.Spec) + + if !desiredSettings.Equal(currentAtlasSettings) { + resp, apiErr := reconcileCtx.svc.Update(ctx, aos.Spec.OrgID, desiredSettings) + if apiErr != nil { + return result.Error(currentState, apiErr) + } + if resp == nil { + return result.Error(currentState, fmt.Errorf("atlas returned OrgSettings which is nil after update")) + } + + return result.NextState(nextState, "Updated") + } + + return result.NextState(nextState, "Ready") +} + +func (h *AtlasOrgSettingsHandler) unmanage(orgID string) (ctrlstate.Result, error) { + return result.NextState(state.StateDeleted, fmt.Sprintf("unmanaged AtlasOrgSettings for orgID %s.", orgID)) +} + +func (h *AtlasOrgSettingsHandler) HandleInitial(ctx context.Context, aos *akov2.AtlasOrgSettings) (ctrlstate.Result, error) { + return h.upsert(ctx, state.StateInitial, state.StateCreated, aos) +} + +func (h *AtlasOrgSettingsHandler) HandleCreated(ctx context.Context, aos *akov2.AtlasOrgSettings) (ctrlstate.Result, error) { + return h.upsert(ctx, state.StateCreated, state.StateUpdated, aos) +} + +func (h *AtlasOrgSettingsHandler) HandleUpdated(ctx context.Context, aos *akov2.AtlasOrgSettings) (ctrlstate.Result, error) { + return h.upsert(ctx, state.StateUpdated, state.StateUpdated, aos) +} + +func (h *AtlasOrgSettingsHandler) HandleDeletionRequested(ctx context.Context, aos *akov2.AtlasOrgSettings) (ctrlstate.Result, error) { + return h.unmanage(aos.Spec.OrgID) +} diff --git a/internal/controller/atlasorgsettings/handler_test.go b/internal/controller/atlasorgsettings/handler_test.go new file mode 100644 index 0000000000..f04caa7d96 --- /dev/null +++ b/internal/controller/atlasorgsettings/handler_test.go @@ -0,0 +1,711 @@ +// 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 atlasorgsettings + +import ( + "context" + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "go.mongodb.org/atlas-sdk/v20250312006/admin" + "go.uber.org/zap" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/mongodb/mongodb-atlas-kubernetes/v2/api" + akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1/status" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/controller/atlas" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/controller/reconciler" + atlasmock "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/mocks/atlas" + mocks "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/mocks/translation" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/pointer" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/atlasorgsettings" + ctrlstate "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/controller/state" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/state" +) + +//nolint:gosec +const ( + fakeOrgID = "fake-org-id" + fakeAPIKey = "fake-api-key" + fakeAPISecret = "fake-api-secret" +) + +var fakeAtlasSecret = corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "atlas-credentials", + Namespace: "default", + }, + Data: map[string][]byte{ + "orgId": []byte(fakeOrgID), + "publicApiKey": []byte(fakeAPIKey), + "privateApiKey": []byte(fakeAPISecret), + }, +} + +var sampleAtlasOrgSettings = akov2.AtlasOrgSettings{ + ObjectMeta: metav1.ObjectMeta{ + Name: "sample-org-settings", + Namespace: "default", + }, + Spec: akov2.AtlasOrgSettingsSpec{ + OrgID: fakeOrgID, + ConnectionSecretRef: &api.LocalObjectReference{ + Name: "atlas-credentials", + }, + ApiAccessListRequired: pointer.MakePtr(true), + GenAIFeaturesEnabled: pointer.MakePtr(true), + MaxServiceAccountSecretValidityInHours: pointer.MakePtr(10), + MultiFactorAuthRequired: pointer.MakePtr(true), + RestrictEmployeeAccess: pointer.MakePtr(true), + SecurityContact: pointer.MakePtr("123@mongodb.com"), + StreamsCrossGroupEnabled: pointer.MakePtr(true), + }, + Status: status.AtlasOrgSettingsStatus{}, +} + +func createSuccessfulProvider() atlas.Provider { + return &atlasmock.TestProvider{ + SdkClientSetFunc: func(ctx context.Context, creds *atlas.Credentials, log *zap.SugaredLogger) (*atlas.ClientSet, error) { + return &atlas.ClientSet{ + SdkClient20250312006: &admin.APIClient{OrganizationsApi: &admin.OrganizationsApiService{}}, + }, nil + }, + } +} + +func createFailingProvider(errorMsg string) atlas.Provider { + return &atlasmock.TestProvider{ + SdkClientSetFunc: func(ctx context.Context, creds *atlas.Credentials, log *zap.SugaredLogger) (*atlas.ClientSet, error) { + return nil, errors.New(errorMsg) + }, + } +} + +// Helper functions for creating common service builders +func createServiceBuilder(t *testing.T, getCurrentReturn *atlasorgsettings.AtlasOrgSettings, getCurrentErr error, + updateReturn *atlasorgsettings.AtlasOrgSettings, updateErr error, expectUpdate bool) func(*atlas.ClientSet) atlasorgsettings.AtlasOrgSettingsService { + return func(_ *atlas.ClientSet) atlasorgsettings.AtlasOrgSettingsService { + service := mocks.NewAtlasOrgSettingsServiceMock(t) + service.EXPECT().Get(mock.Anything, fakeOrgID).Return(getCurrentReturn, getCurrentErr) + + if expectUpdate { + service.EXPECT().Update(mock.Anything, fakeOrgID, mock.Anything).Return(updateReturn, updateErr) + } + return service + } +} + +func TestNewReconcileContext(t *testing.T) { + scheme := runtime.NewScheme() + require.NoError(t, corev1.AddToScheme(scheme)) + require.NoError(t, akov2.AddToScheme(scheme)) + ctx := context.Background() + + tests := []struct { + name string + provider atlas.Provider + serviceBuilder func(*atlas.ClientSet) atlasorgsettings.AtlasOrgSettingsService + input *akov2.AtlasOrgSettings + objects []client.Object + globalSecret client.ObjectKey + wantErr string + expectService bool + }{ + { + name: "successful context creation with connection secret", + provider: createSuccessfulProvider(), + serviceBuilder: func(_ *atlas.ClientSet) atlasorgsettings.AtlasOrgSettingsService { + return mocks.NewAtlasOrgSettingsServiceMock(t) + }, + input: &sampleAtlasOrgSettings, + objects: []client.Object{&fakeAtlasSecret}, + expectService: true, + }, + { + name: "successful context creation with global secret", + provider: createSuccessfulProvider(), + serviceBuilder: func(_ *atlas.ClientSet) atlasorgsettings.AtlasOrgSettingsService { + return mocks.NewAtlasOrgSettingsServiceMock(t) + }, + input: &akov2.AtlasOrgSettings{ + ObjectMeta: metav1.ObjectMeta{ + Name: "no-connection-secret", + Namespace: "default", + }, + Spec: akov2.AtlasOrgSettingsSpec{ + OrgID: fakeOrgID, + // No ConnectionSecretRef - should use global secret + }, + }, + objects: []client.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "global-atlas-credentials", + Namespace: "atlas-system", + }, + Data: map[string][]byte{ + "orgId": []byte(fakeOrgID), + "publicApiKey": []byte(fakeAPIKey), + "privateApiKey": []byte(fakeAPISecret), + }, + }, + }, + globalSecret: client.ObjectKey{ + Name: "global-atlas-credentials", + Namespace: "atlas-system", + }, + expectService: true, + }, + { + name: "context creation with nil connection secret ref", + provider: createSuccessfulProvider(), + serviceBuilder: func(_ *atlas.ClientSet) atlasorgsettings.AtlasOrgSettingsService { + return mocks.NewAtlasOrgSettingsServiceMock(t) + }, + input: &akov2.AtlasOrgSettings{ + ObjectMeta: metav1.ObjectMeta{ + Name: "nil-connection-secret", + Namespace: "default", + }, + Spec: akov2.AtlasOrgSettingsSpec{ + OrgID: fakeOrgID, + ConnectionSecretRef: nil, + }, + }, + objects: []client.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "global-atlas-credentials", + Namespace: "atlas-system", + }, + Data: map[string][]byte{ + "orgId": []byte(fakeOrgID), + "publicApiKey": []byte(fakeAPIKey), + "privateApiKey": []byte(fakeAPISecret), + }, + }, + }, + globalSecret: client.ObjectKey{ + Name: "global-atlas-credentials", + Namespace: "atlas-system", + }, + expectService: true, + }, + { + name: "context creation with empty connection secret name", + provider: createSuccessfulProvider(), + serviceBuilder: func(_ *atlas.ClientSet) atlasorgsettings.AtlasOrgSettingsService { + return mocks.NewAtlasOrgSettingsServiceMock(t) + }, + input: &akov2.AtlasOrgSettings{ + ObjectMeta: metav1.ObjectMeta{ + Name: "empty-secret-name", + Namespace: "default", + }, + Spec: akov2.AtlasOrgSettingsSpec{ + OrgID: fakeOrgID, + ConnectionSecretRef: &api.LocalObjectReference{ + Name: "", // Empty name + }, + }, + }, + objects: []client.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "global-atlas-credentials", + Namespace: "atlas-system", + }, + Data: map[string][]byte{ + "orgId": []byte(fakeOrgID), + "publicApiKey": []byte(fakeAPIKey), + "privateApiKey": []byte(fakeAPISecret), + }, + }, + }, + globalSecret: client.ObjectKey{ + Name: "global-atlas-credentials", + Namespace: "atlas-system", + }, + expectService: true, + }, + { + name: "missing connection secret", + provider: createSuccessfulProvider(), + serviceBuilder: func(_ *atlas.ClientSet) atlasorgsettings.AtlasOrgSettingsService { + return mocks.NewAtlasOrgSettingsServiceMock(t) + }, + input: &akov2.AtlasOrgSettings{ + ObjectMeta: metav1.ObjectMeta{ + Name: "no-secret-org-settings", + Namespace: "default", + }, + Spec: akov2.AtlasOrgSettingsSpec{ + OrgID: fakeOrgID, + ConnectionSecretRef: &api.LocalObjectReference{ + Name: "non-existent-secret", + }, + }, + }, + objects: []client.Object{}, + wantErr: "secrets \"non-existent-secret\" not found", + }, + { + name: "atlas provider sdk client error", + provider: createFailingProvider("SDK initialization failed"), + serviceBuilder: func(_ *atlas.ClientSet) atlasorgsettings.AtlasOrgSettingsService { + return mocks.NewAtlasOrgSettingsServiceMock(t) + }, + input: &sampleAtlasOrgSettings, + objects: []client.Object{&fakeAtlasSecret}, + wantErr: "SDK initialization failed", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + k8sClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(append(tt.objects, tt.input)...). + WithStatusSubresource(tt.input).Build() + + h := &AtlasOrgSettingsHandler{ + AtlasReconciler: reconciler.AtlasReconciler{ + Client: k8sClient, + AtlasProvider: tt.provider, + Log: zap.NewNop().Sugar(), + GlobalSecretRef: tt.globalSecret, + }, + serviceBuilder: tt.serviceBuilder, + } + + reconcileCtx, err := h.newReconcileContext(ctx, tt.input) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + assert.Nil(t, reconcileCtx) + } else { + require.NoError(t, err) + assert.NotNil(t, reconcileCtx) + if tt.expectService { + assert.NotNil(t, reconcileCtx.svc) + } + assert.Equal(t, tt.input, reconcileCtx.aos) + } + }) + } +} + +func TestUpsert(t *testing.T) { + scheme := runtime.NewScheme() + require.NoError(t, corev1.AddToScheme(scheme)) + require.NoError(t, akov2.AddToScheme(scheme)) + ctx := context.Background() + + tests := []struct { + name string + currentState state.ResourceState + nextState state.ResourceState + provider atlas.Provider + serviceBuilder func(*atlas.ClientSet) atlasorgsettings.AtlasOrgSettingsService + input *akov2.AtlasOrgSettings + objects []client.Object + want ctrlstate.Result + wantErr string + }{ + { + name: "successful upsert with settings different - update needed", + currentState: state.StateInitial, + nextState: state.StateCreated, + provider: createSuccessfulProvider(), + serviceBuilder: createServiceBuilder(t, + &atlasorgsettings.AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: fakeOrgID, + ApiAccessListRequired: pointer.MakePtr(false), // Different from sample + }, + }, nil, + &atlasorgsettings.AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: fakeOrgID, + ApiAccessListRequired: pointer.MakePtr(true), + }, + }, nil, true), + input: &sampleAtlasOrgSettings, + objects: []client.Object{&fakeAtlasSecret}, + want: ctrlstate.Result{ + NextState: "Created", + StateMsg: "Updated.", + }, + }, + { + name: "successful upsert with identical settings - no update needed", + currentState: state.StateInitial, + nextState: state.StateCreated, + provider: createSuccessfulProvider(), + serviceBuilder: createServiceBuilder(t, + &atlasorgsettings.AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: fakeOrgID, + ApiAccessListRequired: sampleAtlasOrgSettings.Spec.ApiAccessListRequired, + GenAIFeaturesEnabled: sampleAtlasOrgSettings.Spec.GenAIFeaturesEnabled, + MaxServiceAccountSecretValidityInHours: sampleAtlasOrgSettings.Spec.MaxServiceAccountSecretValidityInHours, + MultiFactorAuthRequired: sampleAtlasOrgSettings.Spec.MultiFactorAuthRequired, + RestrictEmployeeAccess: sampleAtlasOrgSettings.Spec.RestrictEmployeeAccess, + SecurityContact: sampleAtlasOrgSettings.Spec.SecurityContact, + StreamsCrossGroupEnabled: sampleAtlasOrgSettings.Spec.StreamsCrossGroupEnabled, + }, + }, nil, nil, nil, false), // No update expected + input: &sampleAtlasOrgSettings, + objects: []client.Object{&fakeAtlasSecret}, + want: ctrlstate.Result{ + NextState: "Created", + StateMsg: "Ready.", + }, + }, + { + name: "successful upsert with nil current atlas settings", + currentState: state.StateInitial, + nextState: state.StateCreated, + provider: createSuccessfulProvider(), + serviceBuilder: createServiceBuilder(t, nil, nil, + &atlasorgsettings.AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: fakeOrgID, + ApiAccessListRequired: sampleAtlasOrgSettings.Spec.ApiAccessListRequired, + }, + }, nil, true), + input: &sampleAtlasOrgSettings, + objects: []client.Object{&fakeAtlasSecret}, + want: ctrlstate.Result{ + NextState: "Created", + StateMsg: "Updated.", + }, + }, + { + name: "failed reconcile context creation", + currentState: state.StateCreated, + nextState: state.StateUpdated, + provider: createFailingProvider("connection error"), + serviceBuilder: func(_ *atlas.ClientSet) atlasorgsettings.AtlasOrgSettingsService { + return mocks.NewAtlasOrgSettingsServiceMock(t) + }, + input: &sampleAtlasOrgSettings, + objects: []client.Object{&fakeAtlasSecret}, + want: ctrlstate.Result{NextState: "Created"}, + wantErr: "failed to create reconcile context: connection error", + }, + { + name: "get current settings error", + currentState: state.StateInitial, + nextState: state.StateCreated, + provider: createSuccessfulProvider(), + serviceBuilder: createServiceBuilder(t, nil, errors.New("get failed"), nil, nil, false), + input: &sampleAtlasOrgSettings, + objects: []client.Object{&fakeAtlasSecret}, + want: ctrlstate.Result{NextState: "Initial"}, + wantErr: "failed to get current org settings from Atlas: get failed", + }, + { + name: "update error after successful get", + currentState: state.StateInitial, + nextState: state.StateCreated, + provider: createSuccessfulProvider(), + serviceBuilder: createServiceBuilder(t, + &atlasorgsettings.AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: fakeOrgID, + ApiAccessListRequired: pointer.MakePtr(false), // Different from sample + }, + }, nil, nil, errors.New("update failed"), true), + input: &sampleAtlasOrgSettings, + objects: []client.Object{&fakeAtlasSecret}, + want: ctrlstate.Result{NextState: "Initial"}, + wantErr: "update failed", + }, + { + name: "nil response from atlas update service", + currentState: state.StateInitial, + nextState: state.StateCreated, + provider: createSuccessfulProvider(), + serviceBuilder: createServiceBuilder(t, + &atlasorgsettings.AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: fakeOrgID, + ApiAccessListRequired: pointer.MakePtr(false), // Different from sample + }, + }, nil, nil, nil, true), // Update returns nil + input: &sampleAtlasOrgSettings, + objects: []client.Object{&fakeAtlasSecret}, + want: ctrlstate.Result{NextState: "Initial"}, + wantErr: "atlas returned OrgSettings which is nil after update", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + k8sClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(append(tt.objects, tt.input)...). + WithStatusSubresource(tt.input).Build() + + h := &AtlasOrgSettingsHandler{ + AtlasReconciler: reconciler.AtlasReconciler{ + Client: k8sClient, + AtlasProvider: tt.provider, + Log: zap.NewNop().Sugar(), + }, + serviceBuilder: tt.serviceBuilder, + } + + got, err := h.upsert(ctx, tt.currentState, tt.nextState, tt.input) + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + } else { + require.NoError(t, err) + } + assert.Equal(t, tt.want, got) + }) + } +} + +func TestUnmanage(t *testing.T) { + h := &AtlasOrgSettingsHandler{} + + tests := []struct { + name string + orgID string + expected ctrlstate.Result + }{ + { + name: "unmanage with standard org id", + orgID: fakeOrgID, + expected: ctrlstate.Result{ + NextState: "Deleted", + StateMsg: fmt.Sprintf("unmanaged AtlasOrgSettings for orgID %s.", fakeOrgID), + }, + }, + { + name: "unmanage with different org id", + orgID: "another-org-id", + expected: ctrlstate.Result{ + NextState: "Deleted", + StateMsg: "unmanaged AtlasOrgSettings for orgID another-org-id.", + }, + }, + { + name: "unmanage with empty org id", + orgID: "", + expected: ctrlstate.Result{ + NextState: "Deleted", + StateMsg: "unmanaged AtlasOrgSettings for orgID .", + }, + }, + { + name: "unmanage with special characters in org id", + orgID: "org-with-special-chars!@#$%", + expected: ctrlstate.Result{ + NextState: "Deleted", + StateMsg: "unmanaged AtlasOrgSettings for orgID org-with-special-chars!@#$%.", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := h.unmanage(tt.orgID) + require.NoError(t, err) + assert.Equal(t, tt.expected, got) + }) + } +} + +func setupHandlerTest(t *testing.T, scheme *runtime.Scheme) (*AtlasOrgSettingsHandler, context.Context) { + provider := createSuccessfulProvider() + serviceBuilder := createServiceBuilder(t, + &atlasorgsettings.AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: fakeOrgID, + ApiAccessListRequired: pointer.MakePtr(false), // Different from sample + }, + }, nil, + &atlasorgsettings.AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: fakeOrgID, + ApiAccessListRequired: pointer.MakePtr(true), + }, + }, nil, true) + + k8sClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(&fakeAtlasSecret, &sampleAtlasOrgSettings). + WithStatusSubresource(&sampleAtlasOrgSettings).Build() + + h := &AtlasOrgSettingsHandler{ + AtlasReconciler: reconciler.AtlasReconciler{ + Client: k8sClient, + AtlasProvider: provider, + Log: zap.NewNop().Sugar(), + }, + serviceBuilder: serviceBuilder, + } + + return h, context.Background() +} + +func TestHandlerMethods(t *testing.T) { + scheme := runtime.NewScheme() + require.NoError(t, corev1.AddToScheme(scheme)) + require.NoError(t, akov2.AddToScheme(scheme)) + + tests := []struct { + name string + handlerFunc func(*AtlasOrgSettingsHandler, context.Context, *akov2.AtlasOrgSettings) (ctrlstate.Result, error) + expectedResult ctrlstate.Result + }{ + { + name: "HandleInitial", + handlerFunc: (*AtlasOrgSettingsHandler).HandleInitial, + expectedResult: ctrlstate.Result{ + NextState: "Created", + StateMsg: "Updated.", + }, + }, + { + name: "HandleCreated", + handlerFunc: (*AtlasOrgSettingsHandler).HandleCreated, + expectedResult: ctrlstate.Result{ + NextState: "Updated", + StateMsg: "Updated.", + }, + }, + { + name: "HandleUpdated", + handlerFunc: (*AtlasOrgSettingsHandler).HandleUpdated, + expectedResult: ctrlstate.Result{ + NextState: "Updated", + StateMsg: "Updated.", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + h, ctx := setupHandlerTest(t, scheme) + got, err := tt.handlerFunc(h, ctx, &sampleAtlasOrgSettings) + require.NoError(t, err) + assert.Equal(t, tt.expectedResult, got) + }) + } + + // Test HandleDeletionRequested + t.Run("HandleDeletionRequested", func(t *testing.T) { + h := &AtlasOrgSettingsHandler{} + ctx := context.Background() + + got, err := h.HandleDeletionRequested(ctx, &sampleAtlasOrgSettings) + require.NoError(t, err) + assert.Equal(t, ctrlstate.Result{ + NextState: "Deleted", + StateMsg: fmt.Sprintf("unmanaged AtlasOrgSettings for orgID %s.", fakeOrgID), + }, got) + }) +} + +func TestEqualMethodBehaviorInUpsert(t *testing.T) { + scheme := runtime.NewScheme() + require.NoError(t, corev1.AddToScheme(scheme)) + require.NoError(t, akov2.AddToScheme(scheme)) + ctx := context.Background() + + // Test case where Equal method returns false due to nil current settings + t.Run("Equal with nil current settings - update needed", func(t *testing.T) { + provider := createSuccessfulProvider() + serviceBuilder := createServiceBuilder(t, nil, nil, // nil current settings + &atlasorgsettings.AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: fakeOrgID, + ApiAccessListRequired: pointer.MakePtr(true), + }, + }, nil, true) + + k8sClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(&fakeAtlasSecret, &sampleAtlasOrgSettings). + WithStatusSubresource(&sampleAtlasOrgSettings).Build() + + h := &AtlasOrgSettingsHandler{ + AtlasReconciler: reconciler.AtlasReconciler{ + Client: k8sClient, + AtlasProvider: provider, + Log: zap.NewNop().Sugar(), + }, + serviceBuilder: serviceBuilder, + } + + got, err := h.upsert(ctx, state.StateInitial, state.StateCreated, &sampleAtlasOrgSettings) + require.NoError(t, err) + assert.Equal(t, ctrlstate.Result{ + NextState: "Created", + StateMsg: "Updated.", + }, got) + }) + + // Test case with different state transitions + t.Run("Updated to Updated state transition", func(t *testing.T) { + provider := createSuccessfulProvider() + serviceBuilder := createServiceBuilder(t, + &atlasorgsettings.AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: fakeOrgID, + ApiAccessListRequired: pointer.MakePtr(false), // Different from sample + }, + }, nil, + &atlasorgsettings.AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: fakeOrgID, + ApiAccessListRequired: pointer.MakePtr(true), + }, + }, nil, true) + + k8sClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(&fakeAtlasSecret, &sampleAtlasOrgSettings). + WithStatusSubresource(&sampleAtlasOrgSettings).Build() + + h := &AtlasOrgSettingsHandler{ + AtlasReconciler: reconciler.AtlasReconciler{ + Client: k8sClient, + AtlasProvider: provider, + Log: zap.NewNop().Sugar(), + }, + serviceBuilder: serviceBuilder, + } + + got, err := h.upsert(ctx, state.StateUpdated, state.StateUpdated, &sampleAtlasOrgSettings) + require.NoError(t, err) + assert.Equal(t, ctrlstate.Result{ + NextState: "Updated", + StateMsg: "Updated.", + }, got) + }) +} diff --git a/internal/controller/registry.go b/internal/controller/registry.go index a68bec2b5a..631e25e768 100644 --- a/internal/controller/registry.go +++ b/internal/controller/registry.go @@ -37,6 +37,7 @@ import ( "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/controller/atlasipaccesslist" "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/controller/atlasnetworkcontainer" "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/controller/atlasnetworkpeering" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/controller/atlasorgsettings" "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/controller/atlasprivateendpoint" "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/controller/atlasproject" "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/controller/atlassearchindexconfig" @@ -126,6 +127,8 @@ func (r *Registry) registerControllers(c cluster.Cluster, ap atlas.Provider) { reconcilers = append(reconcilers, atlasnetworkcontainer.NewAtlasNetworkContainerReconciler(c, r.defaultPredicates(), ap, r.deletionProtection, r.logger, r.independentSyncPeriod, r.globalSecretRef)) reconcilers = append(reconcilers, atlasnetworkpeering.NewAtlasNetworkPeeringsReconciler(c, r.defaultPredicates(), ap, r.deletionProtection, r.logger, r.independentSyncPeriod, r.globalSecretRef)) + orgSettingsReconciler := atlasorgsettings.NewAtlasOrgSettingsReconciler(c, ap, r.logger, r.globalSecretRef, r.deletionProtection, r.reapplySupport) + reconcilers = append(reconcilers, newCtrlStateReconciler(orgSettingsReconciler)) integrationsReconciler := integrations.NewAtlasThirdPartyIntegrationsReconciler(c, ap, r.deletionProtection, r.logger, r.globalSecretRef, r.reapplySupport) reconcilers = append(reconcilers, newCtrlStateReconciler(integrationsReconciler)) diff --git a/internal/indexer/atlasorgsettingssecrets.go b/internal/indexer/atlasorgsettingssecrets.go new file mode 100644 index 0000000000..103631d4d8 --- /dev/null +++ b/internal/indexer/atlasorgsettingssecrets.go @@ -0,0 +1,38 @@ +// 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 indexer + +import ( + "go.uber.org/zap" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1" +) + +const ( + AtlasOrgSettingsBySecretsIndex = "atlasorgsettings.spec.connectionSecretRef" +) + +func NewAtlasOrgSettingsByConnectionSecretIndexer(logger *zap.Logger) *LocalCredentialIndexer { + return NewLocalCredentialsIndexer(AtlasOrgSettingsBySecretsIndex, &akov2.AtlasOrgSettings{}, logger) +} + +func AtlasOrgSettingsRequest(list *akov2.AtlasOrgSettingsList) []reconcile.Request { + requests := make([]reconcile.Request, 0, len(list.Items)) + for _, item := range list.Items { + requests = append(requests, toRequest(&item)) + } + return requests +} diff --git a/internal/indexer/indexer.go b/internal/indexer/indexer.go index a80200e54d..0d2616f6a9 100644 --- a/internal/indexer/indexer.go +++ b/internal/indexer/indexer.go @@ -70,6 +70,7 @@ func RegisterAll(ctx context.Context, c cluster.Cluster, logger *zap.Logger) err NewAtlasThirdPartyIntegrationByProjectIndexer(logger), NewAtlasThirdPartyIntegrationByCredentialIndexer(logger), NewAtlasThirdPartyIntegrationBySecretsIndexer(logger), + NewAtlasOrgSettingsByConnectionSecretIndexer(logger), ) if version.IsExperimental() { // add experimental indexers here diff --git a/internal/mocks/translation/atlasorgsetting_service.go b/internal/mocks/translation/atlasorgsetting_service.go new file mode 100644 index 0000000000..1cb85feeb8 --- /dev/null +++ b/internal/mocks/translation/atlasorgsetting_service.go @@ -0,0 +1,156 @@ +// Code generated by mockery. DO NOT EDIT. +package translation + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + atlasorgsettings "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/translation/atlasorgsettings" +) + +// AtlasOrgSettingsServiceMock is an autogenerated mock type for the AtlasOrgSettingsService type +type AtlasOrgSettingsServiceMock struct { + mock.Mock +} + +type AtlasOrgSettingsServiceMock_Expecter struct { + mock *mock.Mock +} + +func (_m *AtlasOrgSettingsServiceMock) EXPECT() *AtlasOrgSettingsServiceMock_Expecter { + return &AtlasOrgSettingsServiceMock_Expecter{mock: &_m.Mock} +} + +// Get provides a mock function with given fields: ctx, orgID +func (_m *AtlasOrgSettingsServiceMock) Get(ctx context.Context, orgID string) (*atlasorgsettings.AtlasOrgSettings, error) { + ret := _m.Called(ctx, orgID) + + if len(ret) == 0 { + panic("no return value specified for Get") + } + + var r0 *atlasorgsettings.AtlasOrgSettings + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (*atlasorgsettings.AtlasOrgSettings, error)); ok { + return rf(ctx, orgID) + } + if rf, ok := ret.Get(0).(func(context.Context, string) *atlasorgsettings.AtlasOrgSettings); ok { + r0 = rf(ctx, orgID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*atlasorgsettings.AtlasOrgSettings) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, orgID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// AtlasOrgSettingsServiceMock_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' +type AtlasOrgSettingsServiceMock_Get_Call struct { + *mock.Call +} + +// Get is a helper method to define mock.On call +// - ctx context.Context +// - orgID string +func (_e *AtlasOrgSettingsServiceMock_Expecter) Get(ctx interface{}, orgID interface{}) *AtlasOrgSettingsServiceMock_Get_Call { + return &AtlasOrgSettingsServiceMock_Get_Call{Call: _e.mock.On("Get", ctx, orgID)} +} + +func (_c *AtlasOrgSettingsServiceMock_Get_Call) Run(run func(ctx context.Context, orgID string)) *AtlasOrgSettingsServiceMock_Get_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *AtlasOrgSettingsServiceMock_Get_Call) Return(_a0 *atlasorgsettings.AtlasOrgSettings, _a1 error) *AtlasOrgSettingsServiceMock_Get_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *AtlasOrgSettingsServiceMock_Get_Call) RunAndReturn(run func(context.Context, string) (*atlasorgsettings.AtlasOrgSettings, error)) *AtlasOrgSettingsServiceMock_Get_Call { + _c.Call.Return(run) + return _c +} + +// Update provides a mock function with given fields: ctx, orgID, aos +func (_m *AtlasOrgSettingsServiceMock) Update(ctx context.Context, orgID string, aos *atlasorgsettings.AtlasOrgSettings) (*atlasorgsettings.AtlasOrgSettings, error) { + ret := _m.Called(ctx, orgID, aos) + + if len(ret) == 0 { + panic("no return value specified for Update") + } + + var r0 *atlasorgsettings.AtlasOrgSettings + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, *atlasorgsettings.AtlasOrgSettings) (*atlasorgsettings.AtlasOrgSettings, error)); ok { + return rf(ctx, orgID, aos) + } + if rf, ok := ret.Get(0).(func(context.Context, string, *atlasorgsettings.AtlasOrgSettings) *atlasorgsettings.AtlasOrgSettings); ok { + r0 = rf(ctx, orgID, aos) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*atlasorgsettings.AtlasOrgSettings) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, *atlasorgsettings.AtlasOrgSettings) error); ok { + r1 = rf(ctx, orgID, aos) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// AtlasOrgSettingsServiceMock_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update' +type AtlasOrgSettingsServiceMock_Update_Call struct { + *mock.Call +} + +// Update is a helper method to define mock.On call +// - ctx context.Context +// - orgID string +// - aos *atlasorgsettings.AtlasOrgSettings +func (_e *AtlasOrgSettingsServiceMock_Expecter) Update(ctx interface{}, orgID interface{}, aos interface{}) *AtlasOrgSettingsServiceMock_Update_Call { + return &AtlasOrgSettingsServiceMock_Update_Call{Call: _e.mock.On("Update", ctx, orgID, aos)} +} + +func (_c *AtlasOrgSettingsServiceMock_Update_Call) Run(run func(ctx context.Context, orgID string, aos *atlasorgsettings.AtlasOrgSettings)) *AtlasOrgSettingsServiceMock_Update_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(*atlasorgsettings.AtlasOrgSettings)) + }) + return _c +} + +func (_c *AtlasOrgSettingsServiceMock_Update_Call) Return(_a0 *atlasorgsettings.AtlasOrgSettings, _a1 error) *AtlasOrgSettingsServiceMock_Update_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *AtlasOrgSettingsServiceMock_Update_Call) RunAndReturn(run func(context.Context, string, *atlasorgsettings.AtlasOrgSettings) (*atlasorgsettings.AtlasOrgSettings, error)) *AtlasOrgSettingsServiceMock_Update_Call { + _c.Call.Return(run) + return _c +} + +// NewAtlasOrgSettingsServiceMock creates a new instance of AtlasOrgSettingsServiceMock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewAtlasOrgSettingsServiceMock(t interface { + mock.TestingT + Cleanup(func()) +}) *AtlasOrgSettingsServiceMock { + mock := &AtlasOrgSettingsServiceMock{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/translation/atlasorgsettings/atlasorgsettings.go b/internal/translation/atlasorgsettings/atlasorgsettings.go new file mode 100644 index 0000000000..d986106009 --- /dev/null +++ b/internal/translation/atlasorgsettings/atlasorgsettings.go @@ -0,0 +1,87 @@ +// 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 atlasorgsettings + +import ( + "go.mongodb.org/atlas-sdk/v20250312006/admin" + + akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1" +) + +type AtlasOrgSettings struct { + akov2.AtlasOrgSettingsSpec +} + +func NewFromAKO(spec akov2.AtlasOrgSettingsSpec) *AtlasOrgSettings { + return &AtlasOrgSettings{ + AtlasOrgSettingsSpec: spec, + } +} + +func (a *AtlasOrgSettings) Equal(other *AtlasOrgSettings) bool { + if other == nil { + return false + } + + return a.ApiAccessListRequired != nil && other.ApiAccessListRequired != nil && + *a.ApiAccessListRequired == *other.ApiAccessListRequired && + a.GenAIFeaturesEnabled != nil && other.GenAIFeaturesEnabled != nil && + *a.GenAIFeaturesEnabled == *other.GenAIFeaturesEnabled && + a.MaxServiceAccountSecretValidityInHours != nil && other.MaxServiceAccountSecretValidityInHours != nil && + *a.MaxServiceAccountSecretValidityInHours == *other.MaxServiceAccountSecretValidityInHours && + a.MultiFactorAuthRequired != nil && other.MultiFactorAuthRequired != nil && + *a.MultiFactorAuthRequired == *other.MultiFactorAuthRequired && + a.RestrictEmployeeAccess != nil && other.RestrictEmployeeAccess != nil && + *a.RestrictEmployeeAccess == *other.RestrictEmployeeAccess && + a.SecurityContact != nil && other.SecurityContact != nil && + *a.SecurityContact == *other.SecurityContact && + a.StreamsCrossGroupEnabled != nil && other.StreamsCrossGroupEnabled != nil && + *a.StreamsCrossGroupEnabled == *other.StreamsCrossGroupEnabled +} + +func NewFromAtlas(orgID string, atlasSpec *admin.OrganizationSettings) *AtlasOrgSettings { + if atlasSpec == nil { + return nil + } + + return &AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: orgID, + ConnectionSecretRef: nil, + ApiAccessListRequired: atlasSpec.ApiAccessListRequired, + GenAIFeaturesEnabled: atlasSpec.GenAIFeaturesEnabled, + MaxServiceAccountSecretValidityInHours: atlasSpec.MaxServiceAccountSecretValidityInHours, + MultiFactorAuthRequired: atlasSpec.MultiFactorAuthRequired, + RestrictEmployeeAccess: atlasSpec.RestrictEmployeeAccess, + SecurityContact: atlasSpec.SecurityContact, + StreamsCrossGroupEnabled: atlasSpec.StreamsCrossGroupEnabled, + }, + } +} + +func ToAtlas(orgSettings *AtlasOrgSettings) *admin.OrganizationSettings { + if orgSettings == nil { + return nil + } + + return &admin.OrganizationSettings{ + ApiAccessListRequired: orgSettings.ApiAccessListRequired, + GenAIFeaturesEnabled: orgSettings.GenAIFeaturesEnabled, + MaxServiceAccountSecretValidityInHours: orgSettings.MaxServiceAccountSecretValidityInHours, + MultiFactorAuthRequired: orgSettings.MultiFactorAuthRequired, + RestrictEmployeeAccess: orgSettings.RestrictEmployeeAccess, + SecurityContact: orgSettings.SecurityContact, + } +} diff --git a/internal/translation/atlasorgsettings/atlasorgsettings_test.go b/internal/translation/atlasorgsettings/atlasorgsettings_test.go new file mode 100644 index 0000000000..191572a2fe --- /dev/null +++ b/internal/translation/atlasorgsettings/atlasorgsettings_test.go @@ -0,0 +1,644 @@ +// 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 atlasorgsettings + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "go.mongodb.org/atlas-sdk/v20250312006/admin" + + "github.com/mongodb/mongodb-atlas-kubernetes/v2/api" + akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/pointer" +) + +func TestNewFromAKO(t *testing.T) { + tests := []struct { + name string + spec akov2.AtlasOrgSettingsSpec + expected *AtlasOrgSettings + }{ + { + name: "complete org settings spec", + spec: akov2.AtlasOrgSettingsSpec{ + OrgID: "test-org-id", + ConnectionSecretRef: &api.LocalObjectReference{ + Name: "test-secret", + }, + ApiAccessListRequired: pointer.MakePtr(true), + GenAIFeaturesEnabled: pointer.MakePtr(false), + MaxServiceAccountSecretValidityInHours: pointer.MakePtr(24), + MultiFactorAuthRequired: pointer.MakePtr(true), + RestrictEmployeeAccess: pointer.MakePtr(false), + SecurityContact: pointer.MakePtr("security@example.com"), + StreamsCrossGroupEnabled: pointer.MakePtr(true), + }, + expected: &AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: "test-org-id", + ConnectionSecretRef: &api.LocalObjectReference{ + Name: "test-secret", + }, + ApiAccessListRequired: pointer.MakePtr(true), + GenAIFeaturesEnabled: pointer.MakePtr(false), + MaxServiceAccountSecretValidityInHours: pointer.MakePtr(24), + MultiFactorAuthRequired: pointer.MakePtr(true), + RestrictEmployeeAccess: pointer.MakePtr(false), + SecurityContact: pointer.MakePtr("security@example.com"), + StreamsCrossGroupEnabled: pointer.MakePtr(true), + }, + }, + }, + { + name: "minimal org settings spec", + spec: akov2.AtlasOrgSettingsSpec{ + OrgID: "minimal-org-id", + }, + expected: &AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: "minimal-org-id", + }, + }, + }, + { + name: "empty org settings spec", + spec: akov2.AtlasOrgSettingsSpec{}, + expected: &AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := NewFromAKO(tt.spec) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestNewFromAtlas(t *testing.T) { + tests := []struct { + name string + orgID string + atlasSpec *admin.OrganizationSettings + expected *AtlasOrgSettings + }{ + { + name: "complete atlas organization settings", + orgID: "test-org-id", + atlasSpec: &admin.OrganizationSettings{ + ApiAccessListRequired: pointer.MakePtr(true), + GenAIFeaturesEnabled: pointer.MakePtr(false), + MaxServiceAccountSecretValidityInHours: pointer.MakePtr(48), + MultiFactorAuthRequired: pointer.MakePtr(true), + RestrictEmployeeAccess: pointer.MakePtr(false), + SecurityContact: pointer.MakePtr("security@example.com"), + StreamsCrossGroupEnabled: pointer.MakePtr(true), + }, + expected: &AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: "test-org-id", + ConnectionSecretRef: nil, + ApiAccessListRequired: pointer.MakePtr(true), + GenAIFeaturesEnabled: pointer.MakePtr(false), + MaxServiceAccountSecretValidityInHours: pointer.MakePtr(48), + MultiFactorAuthRequired: pointer.MakePtr(true), + RestrictEmployeeAccess: pointer.MakePtr(false), + SecurityContact: pointer.MakePtr("security@example.com"), + StreamsCrossGroupEnabled: pointer.MakePtr(true), + }, + }, + }, + { + name: "atlas settings with some nil values", + orgID: "test-org-id-2", + atlasSpec: &admin.OrganizationSettings{ + ApiAccessListRequired: pointer.MakePtr(false), + GenAIFeaturesEnabled: nil, + MaxServiceAccountSecretValidityInHours: pointer.MakePtr(0), + MultiFactorAuthRequired: nil, + RestrictEmployeeAccess: pointer.MakePtr(true), + SecurityContact: nil, + StreamsCrossGroupEnabled: pointer.MakePtr(false), + }, + expected: &AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: "test-org-id-2", + ConnectionSecretRef: nil, + ApiAccessListRequired: pointer.MakePtr(false), + GenAIFeaturesEnabled: nil, + MaxServiceAccountSecretValidityInHours: pointer.MakePtr(0), + MultiFactorAuthRequired: nil, + RestrictEmployeeAccess: pointer.MakePtr(true), + SecurityContact: nil, + StreamsCrossGroupEnabled: pointer.MakePtr(false), + }, + }, + }, + { + name: "nil atlas spec returns nil", + orgID: "test-org-id", + atlasSpec: nil, + expected: nil, + }, + { + name: "empty org id with valid atlas spec", + orgID: "", + atlasSpec: &admin.OrganizationSettings{ + ApiAccessListRequired: pointer.MakePtr(true), + }, + expected: &AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: "", + ConnectionSecretRef: nil, + ApiAccessListRequired: pointer.MakePtr(true), + GenAIFeaturesEnabled: nil, + MaxServiceAccountSecretValidityInHours: nil, + MultiFactorAuthRequired: nil, + RestrictEmployeeAccess: nil, + SecurityContact: nil, + StreamsCrossGroupEnabled: nil, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := NewFromAtlas(tt.orgID, tt.atlasSpec) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestToAtlas(t *testing.T) { + tests := []struct { + name string + orgSettings *AtlasOrgSettings + expected *admin.OrganizationSettings + }{ + { + name: "complete org settings to atlas", + orgSettings: &AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: "test-org-id", + ConnectionSecretRef: &api.LocalObjectReference{ + Name: "test-secret", + }, + ApiAccessListRequired: pointer.MakePtr(true), + GenAIFeaturesEnabled: pointer.MakePtr(false), + MaxServiceAccountSecretValidityInHours: pointer.MakePtr(24), + MultiFactorAuthRequired: pointer.MakePtr(true), + RestrictEmployeeAccess: pointer.MakePtr(false), + SecurityContact: pointer.MakePtr("security@example.com"), + StreamsCrossGroupEnabled: pointer.MakePtr(true), + }, + }, + expected: &admin.OrganizationSettings{ + ApiAccessListRequired: pointer.MakePtr(true), + GenAIFeaturesEnabled: pointer.MakePtr(false), + MaxServiceAccountSecretValidityInHours: pointer.MakePtr(24), + MultiFactorAuthRequired: pointer.MakePtr(true), + RestrictEmployeeAccess: pointer.MakePtr(false), + SecurityContact: pointer.MakePtr("security@example.com"), + }, + }, + { + name: "org settings with some nil values", + orgSettings: &AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: "test-org-id", + ApiAccessListRequired: nil, + GenAIFeaturesEnabled: pointer.MakePtr(true), + MaxServiceAccountSecretValidityInHours: nil, + MultiFactorAuthRequired: pointer.MakePtr(false), + RestrictEmployeeAccess: nil, + SecurityContact: pointer.MakePtr("admin@example.com"), + StreamsCrossGroupEnabled: pointer.MakePtr(false), + }, + }, + expected: &admin.OrganizationSettings{ + ApiAccessListRequired: nil, + GenAIFeaturesEnabled: pointer.MakePtr(true), + MaxServiceAccountSecretValidityInHours: nil, + MultiFactorAuthRequired: pointer.MakePtr(false), + RestrictEmployeeAccess: nil, + SecurityContact: pointer.MakePtr("admin@example.com"), + }, + }, + { + name: "org settings with all nil values", + orgSettings: &AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: "test-org-id", + ApiAccessListRequired: nil, + GenAIFeaturesEnabled: nil, + MaxServiceAccountSecretValidityInHours: nil, + MultiFactorAuthRequired: nil, + RestrictEmployeeAccess: nil, + SecurityContact: nil, + StreamsCrossGroupEnabled: nil, + }, + }, + expected: &admin.OrganizationSettings{ + ApiAccessListRequired: nil, + GenAIFeaturesEnabled: nil, + MaxServiceAccountSecretValidityInHours: nil, + MultiFactorAuthRequired: nil, + RestrictEmployeeAccess: nil, + SecurityContact: nil, + }, + }, + { + name: "nil org settings returns nil", + orgSettings: nil, + expected: nil, + }, + { + name: "empty org settings struct", + orgSettings: &AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{}, + }, + expected: &admin.OrganizationSettings{ + ApiAccessListRequired: nil, + GenAIFeaturesEnabled: nil, + MaxServiceAccountSecretValidityInHours: nil, + MultiFactorAuthRequired: nil, + RestrictEmployeeAccess: nil, + SecurityContact: nil, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := ToAtlas(tt.orgSettings) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestAtlasOrgSettings_Equal(t *testing.T) { + // Helper function to create a complete AtlasOrgSettings instance + createCompleteSettings := func() *AtlasOrgSettings { + return &AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + ApiAccessListRequired: pointer.MakePtr(true), + GenAIFeaturesEnabled: pointer.MakePtr(false), + MaxServiceAccountSecretValidityInHours: pointer.MakePtr(24), + MultiFactorAuthRequired: pointer.MakePtr(true), + RestrictEmployeeAccess: pointer.MakePtr(false), + SecurityContact: pointer.MakePtr("security@example.com"), + StreamsCrossGroupEnabled: pointer.MakePtr(true), + }, + } + } + + tests := []struct { + name string + a *AtlasOrgSettings + other *AtlasOrgSettings + expected bool + }{ + { + name: "identical complete settings should be equal", + a: createCompleteSettings(), + other: createCompleteSettings(), + expected: true, + }, + { + name: "comparing with nil should return false", + a: createCompleteSettings(), + other: nil, + expected: false, + }, + { + name: "different ApiAccessListRequired values should not be equal", + a: createCompleteSettings(), + other: func() *AtlasOrgSettings { + s := createCompleteSettings() + s.ApiAccessListRequired = pointer.MakePtr(false) + return s + }(), + expected: false, + }, + { + name: "different GenAIFeaturesEnabled values should not be equal", + a: createCompleteSettings(), + other: func() *AtlasOrgSettings { + s := createCompleteSettings() + s.GenAIFeaturesEnabled = pointer.MakePtr(true) + return s + }(), + expected: false, + }, + { + name: "different MaxServiceAccountSecretValidityInHours values should not be equal", + a: createCompleteSettings(), + other: func() *AtlasOrgSettings { + s := createCompleteSettings() + s.MaxServiceAccountSecretValidityInHours = pointer.MakePtr(48) + return s + }(), + expected: false, + }, + { + name: "different MultiFactorAuthRequired values should not be equal", + a: createCompleteSettings(), + other: func() *AtlasOrgSettings { + s := createCompleteSettings() + s.MultiFactorAuthRequired = pointer.MakePtr(false) + return s + }(), + expected: false, + }, + { + name: "different RestrictEmployeeAccess values should not be equal", + a: createCompleteSettings(), + other: func() *AtlasOrgSettings { + s := createCompleteSettings() + s.RestrictEmployeeAccess = pointer.MakePtr(true) + return s + }(), + expected: false, + }, + { + name: "different SecurityContact values should not be equal", + a: createCompleteSettings(), + other: func() *AtlasOrgSettings { + s := createCompleteSettings() + s.SecurityContact = pointer.MakePtr("different@example.com") + return s + }(), + expected: false, + }, + { + name: "different StreamsCrossGroupEnabled values should not be equal", + a: createCompleteSettings(), + other: func() *AtlasOrgSettings { + s := createCompleteSettings() + s.StreamsCrossGroupEnabled = pointer.MakePtr(false) + return s + }(), + expected: false, + }, + { + name: "nil ApiAccessListRequired in first object should not be equal", + a: func() *AtlasOrgSettings { + s := createCompleteSettings() + s.ApiAccessListRequired = nil + return s + }(), + other: createCompleteSettings(), + expected: false, + }, + { + name: "nil ApiAccessListRequired in second object should not be equal", + a: createCompleteSettings(), + other: func() *AtlasOrgSettings { + s := createCompleteSettings() + s.ApiAccessListRequired = nil + return s + }(), + expected: false, + }, + { + name: "nil GenAIFeaturesEnabled in first object should not be equal", + a: func() *AtlasOrgSettings { + s := createCompleteSettings() + s.GenAIFeaturesEnabled = nil + return s + }(), + other: createCompleteSettings(), + expected: false, + }, + { + name: "nil GenAIFeaturesEnabled in second object should not be equal", + a: createCompleteSettings(), + other: func() *AtlasOrgSettings { + s := createCompleteSettings() + s.GenAIFeaturesEnabled = nil + return s + }(), + expected: false, + }, + { + name: "nil MaxServiceAccountSecretValidityInHours in first object should not be equal", + a: func() *AtlasOrgSettings { + s := createCompleteSettings() + s.MaxServiceAccountSecretValidityInHours = nil + return s + }(), + other: createCompleteSettings(), + expected: false, + }, + { + name: "nil MaxServiceAccountSecretValidityInHours in second object should not be equal", + a: createCompleteSettings(), + other: func() *AtlasOrgSettings { + s := createCompleteSettings() + s.MaxServiceAccountSecretValidityInHours = nil + return s + }(), + expected: false, + }, + { + name: "nil MultiFactorAuthRequired in first object should not be equal", + a: func() *AtlasOrgSettings { + s := createCompleteSettings() + s.MultiFactorAuthRequired = nil + return s + }(), + other: createCompleteSettings(), + expected: false, + }, + { + name: "nil MultiFactorAuthRequired in second object should not be equal", + a: createCompleteSettings(), + other: func() *AtlasOrgSettings { + s := createCompleteSettings() + s.MultiFactorAuthRequired = nil + return s + }(), + expected: false, + }, + { + name: "nil RestrictEmployeeAccess in first object should not be equal", + a: func() *AtlasOrgSettings { + s := createCompleteSettings() + s.RestrictEmployeeAccess = nil + return s + }(), + other: createCompleteSettings(), + expected: false, + }, + { + name: "nil RestrictEmployeeAccess in second object should not be equal", + a: createCompleteSettings(), + other: func() *AtlasOrgSettings { + s := createCompleteSettings() + s.RestrictEmployeeAccess = nil + return s + }(), + expected: false, + }, + { + name: "nil SecurityContact in first object should not be equal", + a: func() *AtlasOrgSettings { + s := createCompleteSettings() + s.SecurityContact = nil + return s + }(), + other: createCompleteSettings(), + expected: false, + }, + { + name: "nil SecurityContact in second object should not be equal", + a: createCompleteSettings(), + other: func() *AtlasOrgSettings { + s := createCompleteSettings() + s.SecurityContact = nil + return s + }(), + expected: false, + }, + { + name: "nil StreamsCrossGroupEnabled in first object should not be equal", + a: func() *AtlasOrgSettings { + s := createCompleteSettings() + s.StreamsCrossGroupEnabled = nil + return s + }(), + other: createCompleteSettings(), + expected: false, + }, + { + name: "nil StreamsCrossGroupEnabled in second object should not be equal", + a: createCompleteSettings(), + other: func() *AtlasOrgSettings { + s := createCompleteSettings() + s.StreamsCrossGroupEnabled = nil + return s + }(), + expected: false, + }, + { + name: "both objects with all nil fields should not be equal", + a: &AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + ApiAccessListRequired: nil, + GenAIFeaturesEnabled: nil, + MaxServiceAccountSecretValidityInHours: nil, + MultiFactorAuthRequired: nil, + RestrictEmployeeAccess: nil, + SecurityContact: nil, + StreamsCrossGroupEnabled: nil, + }, + }, + other: &AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + ApiAccessListRequired: nil, + GenAIFeaturesEnabled: nil, + MaxServiceAccountSecretValidityInHours: nil, + MultiFactorAuthRequired: nil, + RestrictEmployeeAccess: nil, + SecurityContact: nil, + StreamsCrossGroupEnabled: nil, + }, + }, + expected: false, + }, + { + name: "self comparison should be equal", + a: createCompleteSettings(), + other: func() *AtlasOrgSettings { + s := createCompleteSettings() + return s + }(), + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.a.Equal(tt.other) + assert.Equal(t, tt.expected, result, "Expected Equal() to return %v, but got %v", tt.expected, result) + }) + } +} + +func TestRoundTripConversion(t *testing.T) { + t.Run("AKO -> Atlas -> AKO round trip", func(t *testing.T) { + originalAKOSpec := akov2.AtlasOrgSettingsSpec{ + OrgID: "test-org-id", + ConnectionSecretRef: &api.LocalObjectReference{ + Name: "test-secret", + }, + ApiAccessListRequired: pointer.MakePtr(true), + GenAIFeaturesEnabled: pointer.MakePtr(false), + MaxServiceAccountSecretValidityInHours: pointer.MakePtr(24), + MultiFactorAuthRequired: pointer.MakePtr(true), + RestrictEmployeeAccess: pointer.MakePtr(false), + SecurityContact: pointer.MakePtr("security@example.com"), + StreamsCrossGroupEnabled: pointer.MakePtr(true), + } + + // Convert AKO -> AtlasOrgSettings -> Atlas -> AtlasOrgSettings + atlasOrgSettings := NewFromAKO(originalAKOSpec) + atlasFormat := ToAtlas(atlasOrgSettings) + roundTripSettings := NewFromAtlas(originalAKOSpec.OrgID, atlasFormat) + + // Verify core fields are preserved (note: ConnectionSecret is not in Atlas format) + assert.Equal(t, originalAKOSpec.OrgID, roundTripSettings.OrgID) + assert.Equal(t, originalAKOSpec.ApiAccessListRequired, roundTripSettings.ApiAccessListRequired) + assert.Equal(t, originalAKOSpec.GenAIFeaturesEnabled, roundTripSettings.GenAIFeaturesEnabled) + assert.Equal(t, originalAKOSpec.MaxServiceAccountSecretValidityInHours, roundTripSettings.MaxServiceAccountSecretValidityInHours) + assert.Equal(t, originalAKOSpec.MultiFactorAuthRequired, roundTripSettings.MultiFactorAuthRequired) + assert.Equal(t, originalAKOSpec.RestrictEmployeeAccess, roundTripSettings.RestrictEmployeeAccess) + assert.Equal(t, originalAKOSpec.SecurityContact, roundTripSettings.SecurityContact) + + // Note: StreamsCrossGroupEnabled is not included in ToAtlas conversion, so it will be nil + assert.Nil(t, roundTripSettings.StreamsCrossGroupEnabled) + }) + + t.Run("Atlas -> AKO -> Atlas round trip", func(t *testing.T) { + originalAtlasSettings := &admin.OrganizationSettings{ + ApiAccessListRequired: pointer.MakePtr(false), + GenAIFeaturesEnabled: pointer.MakePtr(true), + MaxServiceAccountSecretValidityInHours: pointer.MakePtr(48), + MultiFactorAuthRequired: pointer.MakePtr(false), + RestrictEmployeeAccess: pointer.MakePtr(true), + SecurityContact: pointer.MakePtr("admin@example.com"), + StreamsCrossGroupEnabled: pointer.MakePtr(false), + } + + // Convert Atlas -> AtlasOrgSettings -> Atlas + atlasOrgSettings := NewFromAtlas("test-org", originalAtlasSettings) + roundTripAtlas := ToAtlas(atlasOrgSettings) + + // Verify all fields are preserved except StreamsCrossGroupEnabled (not in ToAtlas) + assert.Equal(t, originalAtlasSettings.ApiAccessListRequired, roundTripAtlas.ApiAccessListRequired) + assert.Equal(t, originalAtlasSettings.GenAIFeaturesEnabled, roundTripAtlas.GenAIFeaturesEnabled) + assert.Equal(t, originalAtlasSettings.MaxServiceAccountSecretValidityInHours, roundTripAtlas.MaxServiceAccountSecretValidityInHours) + assert.Equal(t, originalAtlasSettings.MultiFactorAuthRequired, roundTripAtlas.MultiFactorAuthRequired) + assert.Equal(t, originalAtlasSettings.RestrictEmployeeAccess, roundTripAtlas.RestrictEmployeeAccess) + assert.Equal(t, originalAtlasSettings.SecurityContact, roundTripAtlas.SecurityContact) + + // Note: StreamsCrossGroupEnabled is not included in ToAtlas, so it's not in the round trip + assert.Nil(t, roundTripAtlas.StreamsCrossGroupEnabled) + }) +} diff --git a/internal/translation/atlasorgsettings/service.go b/internal/translation/atlasorgsettings/service.go new file mode 100644 index 0000000000..f075b90e21 --- /dev/null +++ b/internal/translation/atlasorgsettings/service.go @@ -0,0 +1,66 @@ +// 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 atlasorgsettings + +import ( + "context" + "fmt" + + "go.mongodb.org/atlas-sdk/v20250312006/admin" +) + +type AtlasOrgSettingsService interface { + Get(ctx context.Context, orgID string) (*AtlasOrgSettings, error) + Update(ctx context.Context, orgID string, aos *AtlasOrgSettings) (*AtlasOrgSettings, error) +} + +type AtlasOrgSettingsServiceImpl struct { + orgSettingsAPI admin.OrganizationsApi +} + +func NewAtlasOrgSettingsService(api admin.OrganizationsApi) AtlasOrgSettingsService { + return &AtlasOrgSettingsServiceImpl{ + orgSettingsAPI: api, + } +} + +func (a *AtlasOrgSettingsServiceImpl) Get(ctx context.Context, orgID string) (*AtlasOrgSettings, error) { + resp, httpResp, err := a.orgSettingsAPI.GetOrganizationSettings(ctx, orgID).Execute() + if err != nil { + return nil, err + } + if httpResp.StatusCode != 200 { + return nil, fmt.Errorf("failed to get AtlasOrgSettings: %w", err) + } + + return NewFromAtlas(orgID, resp), nil +} + +func (a *AtlasOrgSettingsServiceImpl) Update(ctx context.Context, orgID string, aos *AtlasOrgSettings) (*AtlasOrgSettings, error) { + atlasOrgSettings := ToAtlas(aos) + if atlasOrgSettings == nil { + return nil, nil + } + + resp, httpResp, err := a.orgSettingsAPI.UpdateOrganizationSettings(ctx, orgID, atlasOrgSettings).Execute() + if err != nil { + return nil, fmt.Errorf("failed to update AtlasOrgSettings: %w", err) + } + if httpResp.StatusCode != 201 && httpResp.StatusCode != 200 { + return nil, fmt.Errorf("failed to update AtlasOrgSettings: expected status code 200 or 201. Got: %d", httpResp.StatusCode) + } + + return NewFromAtlas(orgID, resp), nil +} diff --git a/internal/translation/atlasorgsettings/service_test.go b/internal/translation/atlasorgsettings/service_test.go new file mode 100644 index 0000000000..64eab5ef5c --- /dev/null +++ b/internal/translation/atlasorgsettings/service_test.go @@ -0,0 +1,387 @@ +// 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 atlasorgsettings + +import ( + "context" + "errors" + "fmt" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "go.mongodb.org/atlas-sdk/v20250312006/admin" + "go.mongodb.org/atlas-sdk/v20250312006/mockadmin" + + akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/pointer" +) + +const ( + testOrgID = "fake-org-id" +) + +var ( + ErrFakeAPIFailure = errors.New("fake API failure") +) + +func TestNewAtlasOrgSettingsService(t *testing.T) { + mockAPI := &mockadmin.OrganizationsApi{} + + service := NewAtlasOrgSettingsService(mockAPI) + + assert.NotNil(t, service) + assert.IsType(t, &AtlasOrgSettingsServiceImpl{}, service) +} + +func TestAtlasOrgSettingsService_Get(t *testing.T) { + for _, tc := range []struct { + title string + orgID string + api admin.OrganizationsApi + expected *AtlasOrgSettings + expectedError error + }{ + { + title: "successful get organization settings", + orgID: testOrgID, + api: testGetOrgSettingsAPI( + &admin.OrganizationSettings{ + ApiAccessListRequired: pointer.MakePtr(true), + GenAIFeaturesEnabled: pointer.MakePtr(false), + MaxServiceAccountSecretValidityInHours: pointer.MakePtr(24), + MultiFactorAuthRequired: pointer.MakePtr(true), + RestrictEmployeeAccess: pointer.MakePtr(false), + SecurityContact: pointer.MakePtr("security@example.com"), + StreamsCrossGroupEnabled: pointer.MakePtr(true), + }, + http.StatusOK, + nil, + ), + expected: &AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: testOrgID, + ConnectionSecretRef: nil, + ApiAccessListRequired: pointer.MakePtr(true), + GenAIFeaturesEnabled: pointer.MakePtr(false), + MaxServiceAccountSecretValidityInHours: pointer.MakePtr(24), + MultiFactorAuthRequired: pointer.MakePtr(true), + RestrictEmployeeAccess: pointer.MakePtr(false), + SecurityContact: pointer.MakePtr("security@example.com"), + StreamsCrossGroupEnabled: pointer.MakePtr(true), + }, + }, + expectedError: nil, + }, + { + title: "successful get with partial settings", + orgID: testOrgID, + api: testGetOrgSettingsAPI( + &admin.OrganizationSettings{ + ApiAccessListRequired: pointer.MakePtr(false), + GenAIFeaturesEnabled: nil, + MaxServiceAccountSecretValidityInHours: nil, + MultiFactorAuthRequired: pointer.MakePtr(false), + RestrictEmployeeAccess: nil, + SecurityContact: pointer.MakePtr("admin@example.com"), + StreamsCrossGroupEnabled: nil, + }, + http.StatusOK, + nil, + ), + expected: &AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: testOrgID, + ConnectionSecretRef: nil, + ApiAccessListRequired: pointer.MakePtr(false), + GenAIFeaturesEnabled: nil, + MaxServiceAccountSecretValidityInHours: nil, + MultiFactorAuthRequired: pointer.MakePtr(false), + RestrictEmployeeAccess: nil, + SecurityContact: pointer.MakePtr("admin@example.com"), + StreamsCrossGroupEnabled: nil, + }, + }, + expectedError: nil, + }, + { + title: "API failure gets passed through", + orgID: testOrgID, + api: testGetOrgSettingsAPI( + nil, + http.StatusInternalServerError, + ErrFakeAPIFailure, + ), + expected: nil, + expectedError: ErrFakeAPIFailure, + }, + { + title: "non-200 status code with no error returns error", + orgID: testOrgID, + api: testGetOrgSettingsAPI( + &admin.OrganizationSettings{}, + http.StatusNotFound, + nil, + ), + expected: nil, + expectedError: nil, // The service returns the original error (nil) for non-200 status + }, + } { + t.Run(tc.title, func(t *testing.T) { + service := NewAtlasOrgSettingsService(tc.api) + + result, err := service.Get(context.Background(), tc.orgID) + + if tc.expectedError != nil { + assert.Error(t, err) + assert.Equal(t, tc.expectedError, err) + assert.Nil(t, result) + } else { + if tc.expected != nil { + assert.NoError(t, err) + assert.Equal(t, tc.expected, result) + } else { + // For the non-200 status case, we expect nil result but may have nil error too + assert.Nil(t, result) + } + } + }) + } +} + +func TestAtlasOrgSettingsService_Update(t *testing.T) { + inputSettings := &AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: testOrgID, + ApiAccessListRequired: pointer.MakePtr(true), + GenAIFeaturesEnabled: pointer.MakePtr(false), + MaxServiceAccountSecretValidityInHours: pointer.MakePtr(48), + MultiFactorAuthRequired: pointer.MakePtr(true), + RestrictEmployeeAccess: pointer.MakePtr(false), + SecurityContact: pointer.MakePtr("security@example.com"), + StreamsCrossGroupEnabled: pointer.MakePtr(true), + }, + } + + // this is a common function to make golangci-lint happy + createSuccessfulUpdateTest := func(title string, statusCode int) struct { + title string + orgID string + settings *AtlasOrgSettings + api admin.OrganizationsApi + expected *AtlasOrgSettings + expectedError error + } { + return struct { + title string + orgID string + settings *AtlasOrgSettings + api admin.OrganizationsApi + expected *AtlasOrgSettings + expectedError error + }{ + title: title, + orgID: testOrgID, + settings: inputSettings, + api: testUpdateOrgSettingsAPI( + &admin.OrganizationSettings{ + ApiAccessListRequired: pointer.MakePtr(true), + GenAIFeaturesEnabled: pointer.MakePtr(false), + MaxServiceAccountSecretValidityInHours: pointer.MakePtr(48), + MultiFactorAuthRequired: pointer.MakePtr(true), + RestrictEmployeeAccess: pointer.MakePtr(false), + SecurityContact: pointer.MakePtr("security@example.com"), + }, + &admin.OrganizationSettings{ + ApiAccessListRequired: pointer.MakePtr(true), + GenAIFeaturesEnabled: pointer.MakePtr(false), + MaxServiceAccountSecretValidityInHours: pointer.MakePtr(48), + MultiFactorAuthRequired: pointer.MakePtr(true), + RestrictEmployeeAccess: pointer.MakePtr(false), + SecurityContact: pointer.MakePtr("security@example.com"), + StreamsCrossGroupEnabled: pointer.MakePtr(true), + }, + statusCode, + nil, + ), + expected: &AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: testOrgID, + ConnectionSecretRef: nil, + ApiAccessListRequired: pointer.MakePtr(true), + GenAIFeaturesEnabled: pointer.MakePtr(false), + MaxServiceAccountSecretValidityInHours: pointer.MakePtr(48), + MultiFactorAuthRequired: pointer.MakePtr(true), + RestrictEmployeeAccess: pointer.MakePtr(false), + SecurityContact: pointer.MakePtr("security@example.com"), + StreamsCrossGroupEnabled: pointer.MakePtr(true), + }, + }, + expectedError: nil, + } + } + + for _, tc := range []struct { + title string + orgID string + settings *AtlasOrgSettings + api admin.OrganizationsApi + expected *AtlasOrgSettings + expectedError error + }{ + createSuccessfulUpdateTest("successful update organization settings", http.StatusOK), + createSuccessfulUpdateTest("successful update with 201 status code", http.StatusCreated), + { + title: "API failure gets passed through", + orgID: testOrgID, + settings: inputSettings, + api: testUpdateOrgSettingsAPI( + &admin.OrganizationSettings{ + ApiAccessListRequired: pointer.MakePtr(true), + GenAIFeaturesEnabled: pointer.MakePtr(false), + MaxServiceAccountSecretValidityInHours: pointer.MakePtr(48), + MultiFactorAuthRequired: pointer.MakePtr(true), + RestrictEmployeeAccess: pointer.MakePtr(false), + SecurityContact: pointer.MakePtr("security@example.com"), + }, + nil, + http.StatusInternalServerError, + ErrFakeAPIFailure, + ), + expected: nil, + expectedError: fmt.Errorf("failed to update AtlasOrgSettings: %w", ErrFakeAPIFailure), + }, + { + title: "nil settings returns nil", + orgID: testOrgID, + settings: nil, + api: &mockadmin.OrganizationsApi{}, // No expectations set since it shouldn't be called + expected: nil, + expectedError: nil, + }, + { + title: "settings that convert to nil atlas settings return nil", + orgID: testOrgID, + settings: &AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: testOrgID, + // All other fields are nil, so ToAtlas might return nil + }, + }, + api: testUpdateOrgSettingsAPI( + &admin.OrganizationSettings{ + ApiAccessListRequired: nil, + GenAIFeaturesEnabled: nil, + MaxServiceAccountSecretValidityInHours: nil, + MultiFactorAuthRequired: nil, + RestrictEmployeeAccess: nil, + SecurityContact: nil, + }, + &admin.OrganizationSettings{ + ApiAccessListRequired: nil, + GenAIFeaturesEnabled: nil, + MaxServiceAccountSecretValidityInHours: nil, + MultiFactorAuthRequired: nil, + RestrictEmployeeAccess: nil, + SecurityContact: nil, + StreamsCrossGroupEnabled: nil, + }, + http.StatusOK, + nil, + ), + expected: &AtlasOrgSettings{ + AtlasOrgSettingsSpec: akov2.AtlasOrgSettingsSpec{ + OrgID: testOrgID, + ConnectionSecretRef: nil, + ApiAccessListRequired: nil, + GenAIFeaturesEnabled: nil, + MaxServiceAccountSecretValidityInHours: nil, + MultiFactorAuthRequired: nil, + RestrictEmployeeAccess: nil, + SecurityContact: nil, + StreamsCrossGroupEnabled: nil, + }, + }, + expectedError: nil, + }, + { + title: "non-200/201 status code with no error returns error", + orgID: testOrgID, + settings: inputSettings, + api: testUpdateOrgSettingsAPI( + &admin.OrganizationSettings{ + ApiAccessListRequired: pointer.MakePtr(true), + GenAIFeaturesEnabled: pointer.MakePtr(false), + MaxServiceAccountSecretValidityInHours: pointer.MakePtr(48), + MultiFactorAuthRequired: pointer.MakePtr(true), + RestrictEmployeeAccess: pointer.MakePtr(false), + SecurityContact: pointer.MakePtr("security@example.com"), + }, + &admin.OrganizationSettings{}, + http.StatusBadRequest, + nil, + ), + expected: nil, + expectedError: nil, // The service returns the original error (nil) for non-200/201 status + }, + } { + t.Run(tc.title, func(t *testing.T) { + service := NewAtlasOrgSettingsService(tc.api) + + result, err := service.Update(context.Background(), tc.orgID, tc.settings) + + if tc.expectedError != nil { + assert.Error(t, err) + assert.Equal(t, tc.expectedError, err) + assert.Nil(t, result) + } else { + if tc.expected != nil { + assert.NoError(t, err) + assert.Equal(t, tc.expected, result) + } else { + // For cases where we expect nil result + assert.Nil(t, result) + } + } + }) + } +} + +func testGetOrgSettingsAPI(response *admin.OrganizationSettings, statusCode int, err error) admin.OrganizationsApi { + mockAPI := &mockadmin.OrganizationsApi{} + + request := admin.GetOrganizationSettingsApiRequest{ApiService: mockAPI} + mockAPI.EXPECT().GetOrganizationSettings(mock.Anything, testOrgID).Return(request) + mockAPI.EXPECT().GetOrganizationSettingsExecute( + mock.AnythingOfType("admin.GetOrganizationSettingsApiRequest")).Return(response, &http.Response{StatusCode: statusCode}, err) + return mockAPI +} + +func testUpdateOrgSettingsAPI(input *admin.OrganizationSettings, response *admin.OrganizationSettings, statusCode int, err error) admin.OrganizationsApi { + mockAPI := &mockadmin.OrganizationsApi{} + + request := admin.UpdateOrganizationSettingsApiRequest{ApiService: mockAPI} + + if input != nil { + mockAPI.EXPECT().UpdateOrganizationSettings(mock.Anything, testOrgID, input).Return(request) + mockAPI.EXPECT().UpdateOrganizationSettingsExecute( + mock.AnythingOfType("admin.UpdateOrganizationSettingsApiRequest")).Return(response, &http.Response{StatusCode: statusCode}, err) + } else { + mockAPI.EXPECT().UpdateOrganizationSettings(mock.Anything, testOrgID, mock.Anything).Return(request).Maybe() + } + + return mockAPI +} diff --git a/licenses.csv b/licenses.csv index f0ae6fa83d..e69de29bb2 100644 --- a/licenses.csv +++ b/licenses.csv @@ -1,153 +0,0 @@ -cel.dev/expr,https://github.com/google/cel-spec/blob/v0.24.0/LICENSE,Apache-2.0 -cloud.google.com/go/auth,https://github.com/googleapis/google-cloud-go/blob/auth/v0.16.4/auth/LICENSE,Apache-2.0 -cloud.google.com/go/auth/oauth2adapt,https://github.com/googleapis/google-cloud-go/blob/auth/oauth2adapt/v0.2.8/auth/oauth2adapt/LICENSE,Apache-2.0 -cloud.google.com/go/compute/apiv1,https://github.com/googleapis/google-cloud-go/blob/compute/v1.44.0/compute/apiv1/license_codes_client.go,Apache-2.0 -cloud.google.com/go/compute/internal,https://github.com/googleapis/google-cloud-go/blob/compute/v1.44.0/compute/LICENSE,Apache-2.0 -cloud.google.com/go/compute/metadata,https://github.com/googleapis/google-cloud-go/blob/compute/metadata/v0.8.0/compute/metadata/LICENSE,Apache-2.0 -cloud.google.com/go/iam,https://github.com/googleapis/google-cloud-go/blob/iam/v1.5.2/iam/LICENSE,Apache-2.0 -cloud.google.com/go/kms,https://github.com/googleapis/google-cloud-go/blob/kms/v1.22.0/kms/LICENSE,Apache-2.0 -cloud.google.com/go/longrunning,https://github.com/googleapis/google-cloud-go/blob/longrunning/v0.6.7/longrunning/LICENSE,Apache-2.0 -github.com/Azure/azure-sdk-for-go,https://github.com/Azure/azure-sdk-for-go/blob/v68.0.0/LICENSE.txt,MIT -github.com/Azure/azure-sdk-for-go/sdk/azcore,https://github.com/Azure/azure-sdk-for-go/blob/sdk/azcore/v1.18.2/sdk/azcore/LICENSE.txt,MIT -github.com/Azure/azure-sdk-for-go/sdk/azidentity,https://github.com/Azure/azure-sdk-for-go/blob/sdk/azidentity/v1.11.0/sdk/azidentity/LICENSE.txt,MIT -github.com/Azure/azure-sdk-for-go/sdk/internal,https://github.com/Azure/azure-sdk-for-go/blob/sdk/internal/v1.11.2/sdk/internal/LICENSE.txt,MIT -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault,https://github.com/Azure/azure-sdk-for-go/blob/sdk/resourcemanager/keyvault/armkeyvault/v1.5.0/sdk/resourcemanager/keyvault/armkeyvault/LICENSE.txt,MIT -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v2,https://github.com/Azure/azure-sdk-for-go/blob/sdk/resourcemanager/network/armnetwork/v2.2.1/sdk/resourcemanager/network/armnetwork/LICENSE.txt,MIT -github.com/Azure/go-autorest/autorest,https://github.com/Azure/go-autorest/blob/autorest/v0.11.30/autorest/LICENSE,Apache-2.0 -github.com/Azure/go-autorest/autorest/adal,https://github.com/Azure/go-autorest/blob/autorest/adal/v0.9.22/autorest/adal/LICENSE,Apache-2.0 -github.com/Azure/go-autorest/autorest/azure/auth,https://github.com/Azure/go-autorest/blob/autorest/azure/auth/v0.5.13/autorest/azure/auth/LICENSE,Apache-2.0 -github.com/Azure/go-autorest/autorest/azure/cli,https://github.com/Azure/go-autorest/blob/autorest/azure/cli/v0.4.6/autorest/azure/cli/LICENSE,Apache-2.0 -github.com/Azure/go-autorest/autorest/date,https://github.com/Azure/go-autorest/blob/autorest/date/v0.3.0/autorest/date/LICENSE,Apache-2.0 -github.com/Azure/go-autorest/autorest/to,https://github.com/Azure/go-autorest/blob/autorest/to/v0.4.1/autorest/to/LICENSE,Apache-2.0 -github.com/Azure/go-autorest/autorest/validation,https://github.com/Azure/go-autorest/blob/autorest/validation/v0.3.1/autorest/validation/LICENSE,Apache-2.0 -github.com/Azure/go-autorest/logger,https://github.com/Azure/go-autorest/blob/logger/v0.2.1/logger/LICENSE,Apache-2.0 -github.com/Azure/go-autorest/tracing,https://github.com/Azure/go-autorest/blob/tracing/v0.6.0/tracing/LICENSE,Apache-2.0 -github.com/AzureAD/microsoft-authentication-library-for-go/apps,https://github.com/AzureAD/microsoft-authentication-library-for-go/blob/v1.4.2/LICENSE,MIT -github.com/Masterminds/semver,https://github.com/Masterminds/semver/blob/v1.5.0/LICENSE.txt,MIT -github.com/Masterminds/semver/v3,https://github.com/Masterminds/semver/blob/v3.3.1/LICENSE.txt,MIT -github.com/antlr4-go/antlr/v4,https://github.com/antlr4-go/antlr/blob/v4.13.0/LICENSE,BSD-3-Clause -github.com/aws/aws-sdk-go,https://github.com/aws/aws-sdk-go/blob/v1.55.8/LICENSE.txt,Apache-2.0 -github.com/aws/aws-sdk-go/internal/sync/singleflight,https://github.com/aws/aws-sdk-go/blob/v1.55.8/internal/sync/singleflight/LICENSE,BSD-3-Clause -github.com/beorn7/perks/quantile,https://github.com/beorn7/perks/blob/v1.0.1/LICENSE,MIT -github.com/blang/semver/v4,https://github.com/blang/semver/blob/v4.0.0/v4/LICENSE,MIT -github.com/cespare/xxhash/v2,https://github.com/cespare/xxhash/blob/v2.3.0/LICENSE.txt,MIT -github.com/davecgh/go-spew/spew,https://github.com/davecgh/go-spew/blob/d8f796af33cc/LICENSE,ISC -github.com/dimchansky/utfbom,https://github.com/dimchansky/utfbom/blob/v1.1.1/LICENSE,Apache-2.0 -github.com/emicklei/go-restful/v3,https://github.com/emicklei/go-restful/blob/v3.11.0/LICENSE,MIT -github.com/evanphx/json-patch/v5,https://github.com/evanphx/json-patch/blob/v5.9.11/v5/LICENSE,BSD-3-Clause -github.com/felixge/httpsnoop,https://github.com/felixge/httpsnoop/blob/v1.0.4/LICENSE.txt,MIT -github.com/fsnotify/fsnotify,https://github.com/fsnotify/fsnotify/blob/v1.7.0/LICENSE,BSD-3-Clause -github.com/fxamacker/cbor/v2,https://github.com/fxamacker/cbor/blob/v2.7.0/LICENSE,MIT -github.com/go-logr/logr,https://github.com/go-logr/logr/blob/v1.4.3/LICENSE,Apache-2.0 -github.com/go-logr/stdr,https://github.com/go-logr/stdr/blob/v1.2.2/LICENSE,Apache-2.0 -github.com/go-logr/zapr,https://github.com/go-logr/zapr/blob/v1.3.0/LICENSE,Apache-2.0 -github.com/go-openapi/jsonpointer,https://github.com/go-openapi/jsonpointer/blob/v0.21.0/LICENSE,Apache-2.0 -github.com/go-openapi/jsonreference,https://github.com/go-openapi/jsonreference/blob/v0.20.2/LICENSE,Apache-2.0 -github.com/go-openapi/swag,https://github.com/go-openapi/swag/blob/v0.23.0/LICENSE,Apache-2.0 -github.com/go-test/deep,https://github.com/go-test/deep/blob/v1.1.1/LICENSE,MIT -github.com/gogo/protobuf,https://github.com/gogo/protobuf/blob/v1.3.2/LICENSE,BSD-3-Clause -github.com/golang-jwt/jwt/v4,https://github.com/golang-jwt/jwt/blob/v4.5.2/LICENSE,MIT -github.com/golang-jwt/jwt/v5,https://github.com/golang-jwt/jwt/blob/v5.3.0/LICENSE,MIT -github.com/golang/snappy,https://github.com/golang/snappy/blob/v0.0.4/LICENSE,BSD-3-Clause -github.com/google/btree,https://github.com/google/btree/blob/v1.1.3/LICENSE,Apache-2.0 -github.com/google/cel-go,https://github.com/google/cel-go/blob/v0.23.2/LICENSE,Apache-2.0 -github.com/google/gnostic-models,https://github.com/google/gnostic-models/blob/v0.6.9/LICENSE,Apache-2.0 -github.com/google/go-cmp/cmp,https://github.com/google/go-cmp/blob/v0.7.0/LICENSE,BSD-3-Clause -github.com/google/gofuzz,https://github.com/google/gofuzz/blob/v1.2.0/LICENSE,Apache-2.0 -github.com/google/s2a-go,https://github.com/google/s2a-go/blob/v0.1.9/LICENSE.md,Apache-2.0 -github.com/google/uuid,https://github.com/google/uuid/blob/v1.6.0/LICENSE,BSD-3-Clause -github.com/googleapis/enterprise-certificate-proxy/client,https://github.com/googleapis/enterprise-certificate-proxy/blob/v0.3.6/LICENSE,Apache-2.0 -github.com/googleapis/gax-go/v2,https://github.com/googleapis/gax-go/blob/v2.15.0/v2/LICENSE,BSD-3-Clause -github.com/jmespath/go-jmespath,https://github.com/jmespath/go-jmespath/blob/v0.4.0/LICENSE,Apache-2.0 -github.com/josharian/intern,https://github.com/josharian/intern/blob/v1.0.0/license.md,MIT -github.com/json-iterator/go,https://github.com/json-iterator/go/blob/v1.1.12/LICENSE,MIT -github.com/klauspost/compress,https://github.com/klauspost/compress/blob/v1.18.0/LICENSE,Apache-2.0 -github.com/klauspost/compress/internal/snapref,https://github.com/klauspost/compress/blob/v1.18.0/internal/snapref/LICENSE,BSD-3-Clause -github.com/klauspost/compress/zstd/internal/xxhash,https://github.com/klauspost/compress/blob/v1.18.0/zstd/internal/xxhash/LICENSE.txt,MIT -github.com/kylelemons/godebug,https://github.com/kylelemons/godebug/blob/v1.1.0/LICENSE,Apache-2.0 -github.com/mailru/easyjson,https://github.com/mailru/easyjson/blob/v0.7.7/LICENSE,MIT -github.com/mitchellh/go-homedir,https://github.com/mitchellh/go-homedir/blob/v1.1.0/LICENSE,MIT -github.com/modern-go/concurrent,https://github.com/modern-go/concurrent/blob/bacd9c7ef1dd/LICENSE,Apache-2.0 -github.com/modern-go/reflect2,https://github.com/modern-go/reflect2/blob/v1.0.2/LICENSE,Apache-2.0 -github.com/mongodb-forks/digest,https://github.com/mongodb-forks/digest/blob/v1.1.0/COPYING,Apache-2.0 -github.com/mongodb/mongodb-atlas-kubernetes/v2,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/LICENSE,Apache-2.0 -github.com/montanaflynn/stats,https://github.com/montanaflynn/stats/blob/v0.7.1/LICENSE,MIT -github.com/munnerz/goautoneg,https://github.com/munnerz/goautoneg/blob/a7dc8b61c822/LICENSE,BSD-3-Clause -github.com/nsf/jsondiff,https://github.com/nsf/jsondiff/blob/43f6cf3098c1/LICENSE,MIT -github.com/onsi/ginkgo/v2,https://github.com/onsi/ginkgo/blob/v2.24.0/LICENSE,MIT -github.com/onsi/gomega,https://github.com/onsi/gomega/blob/v1.38.0/LICENSE,MIT -github.com/pkg/browser,https://github.com/pkg/browser/blob/5ac0b6a4141c/LICENSE,BSD-2-Clause -github.com/pkg/errors,https://github.com/pkg/errors/blob/v0.9.1/LICENSE,BSD-2-Clause -github.com/pmezard/go-difflib/difflib,https://github.com/pmezard/go-difflib/blob/5d4384ee4fb2/LICENSE,BSD-3-Clause -github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil,https://github.com/prometheus/client_golang/blob/v1.22.0/internal/github.com/golang/gddo/LICENSE,BSD-3-Clause -github.com/prometheus/client_golang/prometheus,https://github.com/prometheus/client_golang/blob/v1.22.0/LICENSE,Apache-2.0 -github.com/prometheus/client_model/go,https://github.com/prometheus/client_model/blob/v0.6.1/LICENSE,Apache-2.0 -github.com/prometheus/common,https://github.com/prometheus/common/blob/v0.62.0/LICENSE,Apache-2.0 -github.com/prometheus/procfs,https://github.com/prometheus/procfs/blob/v0.15.1/LICENSE,Apache-2.0 -github.com/sergi/go-diff/diffmatchpatch,https://github.com/sergi/go-diff/blob/v1.4.0/LICENSE,MIT -github.com/sethvargo/go-password/password,https://github.com/sethvargo/go-password/blob/v0.3.1/LICENSE,MIT -github.com/spf13/cobra,https://github.com/spf13/cobra/blob/v1.8.1/LICENSE.txt,Apache-2.0 -github.com/spf13/pflag,https://github.com/spf13/pflag/blob/v1.0.5/LICENSE,BSD-3-Clause -github.com/stoewer/go-strcase,https://github.com/stoewer/go-strcase/blob/v1.3.0/LICENSE,MIT -github.com/stretchr/objx,https://github.com/stretchr/objx/blob/v0.5.2/LICENSE,MIT -github.com/stretchr/testify,https://github.com/stretchr/testify/blob/v1.10.0/LICENSE,MIT -github.com/x448/float16,https://github.com/x448/float16/blob/v0.8.4/LICENSE,MIT -github.com/xdg-go/pbkdf2,https://github.com/xdg-go/pbkdf2/blob/v1.0.0/LICENSE,Apache-2.0 -github.com/xdg-go/scram,https://github.com/xdg-go/scram/blob/v1.1.2/LICENSE,Apache-2.0 -github.com/xdg-go/stringprep,https://github.com/xdg-go/stringprep/blob/v1.0.4/LICENSE,Apache-2.0 -github.com/youmark/pkcs8,https://github.com/youmark/pkcs8/blob/a2c0da244d78/LICENSE,MIT -github.com/yudai/gojsondiff,https://github.com/yudai/gojsondiff/blob/v1.0.0/LICENSE,MIT -github.com/yudai/golcs,https://github.com/yudai/golcs/blob/ecda9a501e82/LICENSE,MIT -go.mongodb.org/atlas-sdk/v20250312002,https://github.com/mongodb/atlas-sdk-go/blob/v20250312002.0.0/LICENSE,Apache-2.0 -go.mongodb.org/mongo-driver,https://github.com/mongodb/mongo-go-driver/blob/v1.17.4/LICENSE,Apache-2.0 -go.opentelemetry.io/auto/sdk,https://github.com/open-telemetry/opentelemetry-go-instrumentation/blob/sdk/v1.1.0/sdk/LICENSE,Apache-2.0 -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc,https://github.com/open-telemetry/opentelemetry-go-contrib/blob/instrumentation/google.golang.org/grpc/otelgrpc/v0.61.0/instrumentation/google.golang.org/grpc/otelgrpc/LICENSE,Apache-2.0 -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp,https://github.com/open-telemetry/opentelemetry-go-contrib/blob/instrumentation/net/http/otelhttp/v0.61.0/instrumentation/net/http/otelhttp/LICENSE,Apache-2.0 -go.opentelemetry.io/otel,https://github.com/open-telemetry/opentelemetry-go/blob/v1.36.0/LICENSE,Apache-2.0 -go.opentelemetry.io/otel/metric,https://github.com/open-telemetry/opentelemetry-go/blob/metric/v1.36.0/metric/LICENSE,Apache-2.0 -go.opentelemetry.io/otel/trace,https://github.com/open-telemetry/opentelemetry-go/blob/trace/v1.36.0/trace/LICENSE,Apache-2.0 -go.uber.org/multierr,https://github.com/uber-go/multierr/blob/v1.11.0/LICENSE.txt,MIT -go.uber.org/zap,https://github.com/uber-go/zap/blob/v1.27.0/LICENSE,MIT -go.yaml.in/yaml/v2,https://github.com/yaml/go-yaml/blob/v2.4.2/LICENSE,Apache-2.0 -golang.org/x/crypto,https://cs.opensource.google/go/x/crypto/+/v0.41.0:LICENSE,BSD-3-Clause -golang.org/x/exp,https://cs.opensource.google/go/x/exp/+/8a7402ab:LICENSE,BSD-3-Clause -golang.org/x/net,https://cs.opensource.google/go/x/net/+/v0.43.0:LICENSE,BSD-3-Clause -golang.org/x/oauth2,https://cs.opensource.google/go/x/oauth2/+/v0.30.0:LICENSE,BSD-3-Clause -golang.org/x/sync,https://cs.opensource.google/go/x/sync/+/v0.16.0:LICENSE,BSD-3-Clause -golang.org/x/sys,https://cs.opensource.google/go/x/sys/+/v0.35.0:LICENSE,BSD-3-Clause -golang.org/x/term,https://cs.opensource.google/go/x/term/+/v0.34.0:LICENSE,BSD-3-Clause -golang.org/x/text,https://cs.opensource.google/go/x/text/+/v0.28.0:LICENSE,BSD-3-Clause -golang.org/x/time/rate,https://cs.opensource.google/go/x/time/+/v0.12.0:LICENSE,BSD-3-Clause -gomodules.xyz/jsonpatch/v2,https://github.com/gomodules/jsonpatch/blob/v2.4.0/v2/LICENSE,Apache-2.0 -google.golang.org/api,https://github.com/googleapis/google-api-go-client/blob/v0.247.0/LICENSE,BSD-3-Clause -google.golang.org/api/internal/third_party/uritemplates,https://github.com/googleapis/google-api-go-client/blob/v0.247.0/internal/third_party/uritemplates/LICENSE,BSD-3-Clause -google.golang.org/genproto/googleapis,https://github.com/googleapis/go-genproto/blob/513f23925822/LICENSE,Apache-2.0 -google.golang.org/genproto/googleapis/api,https://github.com/googleapis/go-genproto/blob/a7a43d27e69b/googleapis/api/LICENSE,Apache-2.0 -google.golang.org/genproto/googleapis/rpc,https://github.com/googleapis/go-genproto/blob/a7a43d27e69b/googleapis/rpc/LICENSE,Apache-2.0 -google.golang.org/grpc,https://github.com/grpc/grpc-go/blob/v1.74.2/LICENSE,Apache-2.0 -google.golang.org/protobuf,https://github.com/protocolbuffers/protobuf-go/blob/v1.36.7/LICENSE,BSD-3-Clause -gopkg.in/evanphx/json-patch.v4,https://github.com/evanphx/json-patch/blob/v4.12.0/LICENSE,BSD-3-Clause -gopkg.in/inf.v0,https://github.com/go-inf/inf/blob/v0.9.1/LICENSE,BSD-3-Clause -gopkg.in/yaml.v2,https://github.com/go-yaml/yaml/blob/v2.4.0/LICENSE,Apache-2.0 -gopkg.in/yaml.v3,https://github.com/go-yaml/yaml/blob/v3.0.1/LICENSE,MIT -k8s.io/api,https://github.com/kubernetes/api/blob/v0.33.4/LICENSE,Apache-2.0 -k8s.io/apiextensions-apiserver/pkg,https://github.com/kubernetes/apiextensions-apiserver/blob/v0.33.4/LICENSE,Apache-2.0 -k8s.io/apimachinery/pkg,https://github.com/kubernetes/apimachinery/blob/v0.33.4/LICENSE,Apache-2.0 -k8s.io/apimachinery/third_party/forked/golang,https://github.com/kubernetes/apimachinery/blob/v0.33.4/third_party/forked/golang/LICENSE,BSD-3-Clause -k8s.io/apiserver/pkg,https://github.com/kubernetes/apiserver/blob/v0.33.4/LICENSE,Apache-2.0 -k8s.io/client-go,https://github.com/kubernetes/client-go/blob/v0.33.4/LICENSE,Apache-2.0 -k8s.io/component-base,https://github.com/kubernetes/component-base/blob/v0.33.4/LICENSE,Apache-2.0 -k8s.io/klog/v2,https://github.com/kubernetes/klog/blob/v2.130.1/LICENSE,Apache-2.0 -k8s.io/kube-openapi/pkg,https://github.com/kubernetes/kube-openapi/blob/c8a335a9a2ff/LICENSE,Apache-2.0 -k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json,https://github.com/kubernetes/kube-openapi/blob/c8a335a9a2ff/pkg/internal/third_party/go-json-experiment/json/LICENSE,BSD-3-Clause -k8s.io/kube-openapi/pkg/internal/third_party/govalidator,https://github.com/kubernetes/kube-openapi/blob/c8a335a9a2ff/pkg/internal/third_party/govalidator/LICENSE,MIT -k8s.io/kube-openapi/pkg/validation/errors,https://github.com/kubernetes/kube-openapi/blob/c8a335a9a2ff/pkg/validation/errors/LICENSE,Apache-2.0 -k8s.io/kube-openapi/pkg/validation/spec,https://github.com/kubernetes/kube-openapi/blob/c8a335a9a2ff/pkg/validation/spec/LICENSE,Apache-2.0 -k8s.io/kube-openapi/pkg/validation/strfmt,https://github.com/kubernetes/kube-openapi/blob/c8a335a9a2ff/pkg/validation/strfmt/LICENSE,Apache-2.0 -k8s.io/utils,https://github.com/kubernetes/utils/blob/3ea5e8cea738/LICENSE,Apache-2.0 -k8s.io/utils/internal/third_party/forked/golang,https://github.com/kubernetes/utils/blob/3ea5e8cea738/internal/third_party/forked/golang/LICENSE,BSD-3-Clause -sigs.k8s.io/controller-runtime,https://github.com/kubernetes-sigs/controller-runtime/blob/v0.21.0/LICENSE,Apache-2.0 -sigs.k8s.io/json,https://github.com/kubernetes-sigs/json/blob/9aa6b5e7a4b3/LICENSE,Apache-2.0 -sigs.k8s.io/randfill,https://github.com/kubernetes-sigs/randfill/blob/v1.0.0/LICENSE,Apache-2.0 -sigs.k8s.io/structured-merge-diff/v4,https://github.com/kubernetes-sigs/structured-merge-diff/blob/v4.6.0/LICENSE,Apache-2.0 -sigs.k8s.io/yaml,https://github.com/kubernetes-sigs/yaml/blob/v1.6.0/LICENSE,Apache-2.0 diff --git a/tools/clean/go.mod b/tools/clean/go.mod index c55a979143..28ba3342a2 100644 --- a/tools/clean/go.mod +++ b/tools/clean/go.mod @@ -38,6 +38,7 @@ require ( github.com/mongodb-forks/digest v1.1.0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/rivo/uniseg v0.4.7 // indirect + go.mongodb.org/atlas-sdk/v20250312006 v20250312006.0.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect diff --git a/tools/clean/go.sum b/tools/clean/go.sum index 30410dd123..5b13d078a8 100644 --- a/tools/clean/go.sum +++ b/tools/clean/go.sum @@ -88,6 +88,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= go.mongodb.org/atlas-sdk/v20250312002 v20250312002.0.0 h1:KX8PrYp3/PCSxG4NbGLcc3+EsNcfyhcvylGbe/oRlx8= go.mongodb.org/atlas-sdk/v20250312002 v20250312002.0.0/go.mod h1:HHCmHxHPdJRr1bUXlvRIZbm7M4gRujjur1GnjE44YgA= +go.mongodb.org/atlas-sdk/v20250312006 v20250312006.0.0 h1:3y9pfi0UYVTz/44lhtVQkvDWg/QMB+jOoxA7poab1nU= +go.mongodb.org/atlas-sdk/v20250312006 v20250312006.0.0/go.mod h1:UZYSaCimjGs3j+wMwgHSKUSIvoJXzmy/xrer0t5TLgo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= From 95796e539e6b6fda0c18306b2f6ca706cf05aaba Mon Sep 17 00:00:00 2001 From: Igor Karpukhin Date: Wed, 20 Aug 2025 14:56:59 +0200 Subject: [PATCH 2/7] Updated licenses --- .licenses-gomod.sha256 | 2 +- licenses.csv | 154 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+), 1 deletion(-) diff --git a/.licenses-gomod.sha256 b/.licenses-gomod.sha256 index 860c553f93..b46e56589e 100644 --- a/.licenses-gomod.sha256 +++ b/.licenses-gomod.sha256 @@ -1 +1 @@ -100644 849a89d4758d68fa1865d1c95a7373fdc76b89fd go.mod +100644 2691da691dfa0e25e9338f3410b5b9db3db87f92 go.mod diff --git a/licenses.csv b/licenses.csv index e69de29bb2..de32bfab36 100644 --- a/licenses.csv +++ b/licenses.csv @@ -0,0 +1,154 @@ +cel.dev/expr,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/cel.dev/expr/LICENSE,Apache-2.0 +cloud.google.com/go/auth,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/cloud.google.com/go/auth/LICENSE,Apache-2.0 +cloud.google.com/go/auth/oauth2adapt,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/cloud.google.com/go/auth/oauth2adapt/LICENSE,Apache-2.0 +cloud.google.com/go/compute/apiv1,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/cloud.google.com/go/compute/apiv1/license_codes_client.go,Apache-2.0 +cloud.google.com/go/compute/internal,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/cloud.google.com/go/compute/LICENSE,Apache-2.0 +cloud.google.com/go/compute/metadata,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/cloud.google.com/go/compute/metadata/LICENSE,Apache-2.0 +cloud.google.com/go/iam,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/cloud.google.com/go/iam/LICENSE,Apache-2.0 +cloud.google.com/go/kms,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/cloud.google.com/go/kms/LICENSE,Apache-2.0 +cloud.google.com/go/longrunning,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/cloud.google.com/go/longrunning/LICENSE,Apache-2.0 +github.com/Azure/azure-sdk-for-go,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/Azure/azure-sdk-for-go/LICENSE.txt,MIT +github.com/Azure/azure-sdk-for-go/sdk/azcore,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/LICENSE.txt,MIT +github.com/Azure/azure-sdk-for-go/sdk/azidentity,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/LICENSE.txt,MIT +github.com/Azure/azure-sdk-for-go/sdk/internal,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/Azure/azure-sdk-for-go/sdk/internal/LICENSE.txt,MIT +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/keyvault/armkeyvault/LICENSE.txt,MIT +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v2,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork/v2/LICENSE.txt,MIT +github.com/Azure/go-autorest/autorest,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/Azure/go-autorest/autorest/LICENSE,Apache-2.0 +github.com/Azure/go-autorest/autorest/adal,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/Azure/go-autorest/autorest/adal/LICENSE,Apache-2.0 +github.com/Azure/go-autorest/autorest/azure/auth,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/Azure/go-autorest/autorest/azure/auth/LICENSE,Apache-2.0 +github.com/Azure/go-autorest/autorest/azure/cli,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/Azure/go-autorest/autorest/azure/cli/LICENSE,Apache-2.0 +github.com/Azure/go-autorest/autorest/date,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/Azure/go-autorest/autorest/date/LICENSE,Apache-2.0 +github.com/Azure/go-autorest/autorest/to,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/Azure/go-autorest/autorest/to/LICENSE,Apache-2.0 +github.com/Azure/go-autorest/autorest/validation,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/Azure/go-autorest/autorest/validation/LICENSE,Apache-2.0 +github.com/Azure/go-autorest/logger,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/Azure/go-autorest/logger/LICENSE,Apache-2.0 +github.com/Azure/go-autorest/tracing,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/Azure/go-autorest/tracing/LICENSE,Apache-2.0 +github.com/AzureAD/microsoft-authentication-library-for-go/apps,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/AzureAD/microsoft-authentication-library-for-go/LICENSE,MIT +github.com/Masterminds/semver,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/Masterminds/semver/LICENSE.txt,MIT +github.com/Masterminds/semver/v3,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/Masterminds/semver/v3/LICENSE.txt,MIT +github.com/antlr4-go/antlr/v4,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/antlr4-go/antlr/v4/LICENSE,BSD-3-Clause +github.com/aws/aws-sdk-go,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/aws/aws-sdk-go/LICENSE.txt,Apache-2.0 +github.com/aws/aws-sdk-go/internal/sync/singleflight,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/aws/aws-sdk-go/internal/sync/singleflight/LICENSE,BSD-3-Clause +github.com/beorn7/perks/quantile,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/beorn7/perks/LICENSE,MIT +github.com/blang/semver/v4,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/blang/semver/v4/LICENSE,MIT +github.com/cespare/xxhash/v2,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/cespare/xxhash/v2/LICENSE.txt,MIT +github.com/davecgh/go-spew/spew,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/davecgh/go-spew/LICENSE,ISC +github.com/dimchansky/utfbom,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/dimchansky/utfbom/LICENSE,Apache-2.0 +github.com/emicklei/go-restful/v3,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/emicklei/go-restful/v3/LICENSE,MIT +github.com/evanphx/json-patch/v5,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/evanphx/json-patch/v5/LICENSE,BSD-3-Clause +github.com/felixge/httpsnoop,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/felixge/httpsnoop/LICENSE.txt,MIT +github.com/fsnotify/fsnotify,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/fsnotify/fsnotify/LICENSE,BSD-3-Clause +github.com/fxamacker/cbor/v2,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/fxamacker/cbor/v2/LICENSE,MIT +github.com/go-logr/logr,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/go-logr/logr/LICENSE,Apache-2.0 +github.com/go-logr/stdr,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/go-logr/stdr/LICENSE,Apache-2.0 +github.com/go-logr/zapr,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/go-logr/zapr/LICENSE,Apache-2.0 +github.com/go-openapi/jsonpointer,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/go-openapi/jsonpointer/LICENSE,Apache-2.0 +github.com/go-openapi/jsonreference,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/go-openapi/jsonreference/LICENSE,Apache-2.0 +github.com/go-openapi/swag,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/go-openapi/swag/LICENSE,Apache-2.0 +github.com/go-test/deep,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/go-test/deep/LICENSE,MIT +github.com/gogo/protobuf,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/gogo/protobuf/LICENSE,BSD-3-Clause +github.com/golang-jwt/jwt/v4,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/golang-jwt/jwt/v4/LICENSE,MIT +github.com/golang-jwt/jwt/v5,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/golang-jwt/jwt/v5/LICENSE,MIT +github.com/golang/snappy,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/golang/snappy/LICENSE,BSD-3-Clause +github.com/google/btree,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/google/btree/LICENSE,Apache-2.0 +github.com/google/cel-go,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/google/cel-go/LICENSE,Apache-2.0 +github.com/google/gnostic-models,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/google/gnostic-models/LICENSE,Apache-2.0 +github.com/google/go-cmp/cmp,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/google/go-cmp/LICENSE,BSD-3-Clause +github.com/google/gofuzz,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/google/gofuzz/LICENSE,Apache-2.0 +github.com/google/s2a-go,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/google/s2a-go/LICENSE.md,Apache-2.0 +github.com/google/uuid,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/google/uuid/LICENSE,BSD-3-Clause +github.com/googleapis/enterprise-certificate-proxy/client,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/googleapis/enterprise-certificate-proxy/LICENSE,Apache-2.0 +github.com/googleapis/gax-go/v2,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/googleapis/gax-go/v2/LICENSE,BSD-3-Clause +github.com/jmespath/go-jmespath,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/jmespath/go-jmespath/LICENSE,Apache-2.0 +github.com/josharian/intern,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/josharian/intern/license.md,MIT +github.com/json-iterator/go,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/json-iterator/go/LICENSE,MIT +github.com/klauspost/compress,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/klauspost/compress/LICENSE,Apache-2.0 +github.com/klauspost/compress/internal/snapref,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/klauspost/compress/internal/snapref/LICENSE,BSD-3-Clause +github.com/klauspost/compress/zstd/internal/xxhash,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/klauspost/compress/zstd/internal/xxhash/LICENSE.txt,MIT +github.com/kylelemons/godebug,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/kylelemons/godebug/LICENSE,Apache-2.0 +github.com/mailru/easyjson,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/mailru/easyjson/LICENSE,MIT +github.com/mitchellh/go-homedir,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/mitchellh/go-homedir/LICENSE,MIT +github.com/modern-go/concurrent,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/modern-go/concurrent/LICENSE,Apache-2.0 +github.com/modern-go/reflect2,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/modern-go/reflect2/LICENSE,Apache-2.0 +github.com/mongodb-forks/digest,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/mongodb-forks/digest/COPYING,Apache-2.0 +github.com/mongodb/mongodb-atlas-kubernetes/v2,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/LICENSE,Apache-2.0 +github.com/montanaflynn/stats,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/montanaflynn/stats/LICENSE,MIT +github.com/munnerz/goautoneg,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/munnerz/goautoneg/LICENSE,BSD-3-Clause +github.com/nsf/jsondiff,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/nsf/jsondiff/LICENSE,MIT +github.com/onsi/ginkgo/v2,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/onsi/ginkgo/v2/LICENSE,MIT +github.com/onsi/gomega,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/onsi/gomega/LICENSE,MIT +github.com/pkg/browser,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/pkg/browser/LICENSE,BSD-2-Clause +github.com/pkg/errors,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/pkg/errors/LICENSE,BSD-2-Clause +github.com/pmezard/go-difflib/difflib,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/pmezard/go-difflib/LICENSE,BSD-3-Clause +github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/prometheus/client_golang/internal/github.com/golang/gddo/LICENSE,BSD-3-Clause +github.com/prometheus/client_golang/prometheus,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/prometheus/client_golang/LICENSE,Apache-2.0 +github.com/prometheus/client_model/go,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/prometheus/client_model/LICENSE,Apache-2.0 +github.com/prometheus/common,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/prometheus/common/LICENSE,Apache-2.0 +github.com/prometheus/procfs,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/prometheus/procfs/LICENSE,Apache-2.0 +github.com/sergi/go-diff/diffmatchpatch,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/sergi/go-diff/LICENSE,MIT +github.com/sethvargo/go-password/password,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/sethvargo/go-password/LICENSE,MIT +github.com/spf13/cobra,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/spf13/cobra/LICENSE.txt,Apache-2.0 +github.com/spf13/pflag,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/spf13/pflag/LICENSE,BSD-3-Clause +github.com/stoewer/go-strcase,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/stoewer/go-strcase/LICENSE,MIT +github.com/stretchr/objx,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/stretchr/objx/LICENSE,MIT +github.com/stretchr/testify,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/stretchr/testify/LICENSE,MIT +github.com/x448/float16,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/x448/float16/LICENSE,MIT +github.com/xdg-go/pbkdf2,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/xdg-go/pbkdf2/LICENSE,Apache-2.0 +github.com/xdg-go/scram,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/xdg-go/scram/LICENSE,Apache-2.0 +github.com/xdg-go/stringprep,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/xdg-go/stringprep/LICENSE,Apache-2.0 +github.com/youmark/pkcs8,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/youmark/pkcs8/LICENSE,MIT +github.com/yudai/gojsondiff,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/yudai/gojsondiff/LICENSE,MIT +github.com/yudai/golcs,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/github.com/yudai/golcs/LICENSE,MIT +go.mongodb.org/atlas-sdk/v20250312002,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/go.mongodb.org/atlas-sdk/v20250312002/LICENSE,Apache-2.0 +go.mongodb.org/atlas-sdk/v20250312006,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/go.mongodb.org/atlas-sdk/v20250312006/LICENSE,Apache-2.0 +go.mongodb.org/mongo-driver,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/go.mongodb.org/mongo-driver/LICENSE,Apache-2.0 +go.opentelemetry.io/auto/sdk,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/go.opentelemetry.io/auto/sdk/LICENSE,Apache-2.0 +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc/LICENSE,Apache-2.0 +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/LICENSE,Apache-2.0 +go.opentelemetry.io/otel,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/go.opentelemetry.io/otel/LICENSE,Apache-2.0 +go.opentelemetry.io/otel/metric,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/go.opentelemetry.io/otel/metric/LICENSE,Apache-2.0 +go.opentelemetry.io/otel/trace,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/go.opentelemetry.io/otel/trace/LICENSE,Apache-2.0 +go.uber.org/multierr,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/go.uber.org/multierr/LICENSE.txt,MIT +go.uber.org/zap,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/go.uber.org/zap/LICENSE,MIT +go.yaml.in/yaml/v2,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/go.yaml.in/yaml/v2/LICENSE,Apache-2.0 +golang.org/x/crypto,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/golang.org/x/crypto/LICENSE,BSD-3-Clause +golang.org/x/exp,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/golang.org/x/exp/LICENSE,BSD-3-Clause +golang.org/x/net,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/golang.org/x/net/LICENSE,BSD-3-Clause +golang.org/x/oauth2,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/golang.org/x/oauth2/LICENSE,BSD-3-Clause +golang.org/x/sync,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/golang.org/x/sync/LICENSE,BSD-3-Clause +golang.org/x/sys/unix,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/golang.org/x/sys/LICENSE,BSD-3-Clause +golang.org/x/term,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/golang.org/x/term/LICENSE,BSD-3-Clause +golang.org/x/text,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/golang.org/x/text/LICENSE,BSD-3-Clause +golang.org/x/time/rate,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/golang.org/x/time/LICENSE,BSD-3-Clause +gomodules.xyz/jsonpatch/v2,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/gomodules.xyz/jsonpatch/v2/LICENSE,Apache-2.0 +google.golang.org/api,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/google.golang.org/api/LICENSE,BSD-3-Clause +google.golang.org/api/internal/third_party/uritemplates,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/google.golang.org/api/internal/third_party/uritemplates/LICENSE,BSD-3-Clause +google.golang.org/genproto/googleapis,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/google.golang.org/genproto/LICENSE,Apache-2.0 +google.golang.org/genproto/googleapis/api,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/google.golang.org/genproto/googleapis/api/LICENSE,Apache-2.0 +google.golang.org/genproto/googleapis/rpc,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/google.golang.org/genproto/googleapis/rpc/LICENSE,Apache-2.0 +google.golang.org/grpc,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/google.golang.org/grpc/LICENSE,Apache-2.0 +google.golang.org/protobuf,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/google.golang.org/protobuf/LICENSE,BSD-3-Clause +gopkg.in/evanphx/json-patch.v4,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/gopkg.in/evanphx/json-patch.v4/LICENSE,BSD-3-Clause +gopkg.in/inf.v0,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/gopkg.in/inf.v0/LICENSE,BSD-3-Clause +gopkg.in/yaml.v2,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/gopkg.in/yaml.v2/LICENSE,Apache-2.0 +gopkg.in/yaml.v3,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/gopkg.in/yaml.v3/LICENSE,MIT +k8s.io/api,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/k8s.io/api/LICENSE,Apache-2.0 +k8s.io/apiextensions-apiserver/pkg,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/k8s.io/apiextensions-apiserver/LICENSE,Apache-2.0 +k8s.io/apimachinery/pkg,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/k8s.io/apimachinery/LICENSE,Apache-2.0 +k8s.io/apimachinery/third_party/forked/golang,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/k8s.io/apimachinery/third_party/forked/golang/LICENSE,BSD-3-Clause +k8s.io/apiserver/pkg,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/k8s.io/apiserver/LICENSE,Apache-2.0 +k8s.io/client-go,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/k8s.io/client-go/LICENSE,Apache-2.0 +k8s.io/component-base,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/k8s.io/component-base/LICENSE,Apache-2.0 +k8s.io/klog/v2,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/k8s.io/klog/v2/LICENSE,Apache-2.0 +k8s.io/kube-openapi/pkg,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/k8s.io/kube-openapi/LICENSE,Apache-2.0 +k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json/LICENSE,BSD-3-Clause +k8s.io/kube-openapi/pkg/internal/third_party/govalidator,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/k8s.io/kube-openapi/pkg/internal/third_party/govalidator/LICENSE,MIT +k8s.io/kube-openapi/pkg/validation/errors,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/k8s.io/kube-openapi/pkg/validation/errors/LICENSE,Apache-2.0 +k8s.io/kube-openapi/pkg/validation/spec,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/k8s.io/kube-openapi/pkg/validation/spec/LICENSE,Apache-2.0 +k8s.io/kube-openapi/pkg/validation/strfmt,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/k8s.io/kube-openapi/pkg/validation/strfmt/LICENSE,Apache-2.0 +k8s.io/utils,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/k8s.io/utils/LICENSE,Apache-2.0 +k8s.io/utils/internal/third_party/forked/golang,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/k8s.io/utils/internal/third_party/forked/golang/LICENSE,BSD-3-Clause +sigs.k8s.io/controller-runtime,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/sigs.k8s.io/controller-runtime/LICENSE,Apache-2.0 +sigs.k8s.io/json,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/sigs.k8s.io/json/LICENSE,Apache-2.0 +sigs.k8s.io/randfill,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/sigs.k8s.io/randfill/LICENSE,Apache-2.0 +sigs.k8s.io/structured-merge-diff/v4,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/sigs.k8s.io/structured-merge-diff/v4/LICENSE,Apache-2.0 +sigs.k8s.io/yaml,https://github.com/mongodb/mongodb-atlas-kubernetes/blob/HEAD/vendor/sigs.k8s.io/yaml/LICENSE,Apache-2.0 From d037692a2484c36b698d2f7f18c2cd1accbfb015 Mon Sep 17 00:00:00 2001 From: Igor Karpukhin Date: Wed, 20 Aug 2025 15:00:46 +0200 Subject: [PATCH 3/7] Fixed a leftover --- internal/translation/atlasorgsettings/service.go | 4 ++-- internal/translation/atlasorgsettings/service_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/translation/atlasorgsettings/service.go b/internal/translation/atlasorgsettings/service.go index f075b90e21..8165b14441 100644 --- a/internal/translation/atlasorgsettings/service.go +++ b/internal/translation/atlasorgsettings/service.go @@ -39,10 +39,10 @@ func NewAtlasOrgSettingsService(api admin.OrganizationsApi) AtlasOrgSettingsServ func (a *AtlasOrgSettingsServiceImpl) Get(ctx context.Context, orgID string) (*AtlasOrgSettings, error) { resp, httpResp, err := a.orgSettingsAPI.GetOrganizationSettings(ctx, orgID).Execute() if err != nil { - return nil, err + return nil, fmt.Errorf("failed to get AtlasOrgSettings: %w", err) } if httpResp.StatusCode != 200 { - return nil, fmt.Errorf("failed to get AtlasOrgSettings: %w", err) + return nil, fmt.Errorf("failed to get AtlasOrgSettings: expected status code 200. Got: %d", httpResp.StatusCode) } return NewFromAtlas(orgID, resp), nil diff --git a/internal/translation/atlasorgsettings/service_test.go b/internal/translation/atlasorgsettings/service_test.go index 64eab5ef5c..93866102c4 100644 --- a/internal/translation/atlasorgsettings/service_test.go +++ b/internal/translation/atlasorgsettings/service_test.go @@ -126,7 +126,7 @@ func TestAtlasOrgSettingsService_Get(t *testing.T) { ErrFakeAPIFailure, ), expected: nil, - expectedError: ErrFakeAPIFailure, + expectedError: fmt.Errorf("failed to get AtlasOrgSettings: %w", ErrFakeAPIFailure), }, { title: "non-200 status code with no error returns error", From 98f6ca81e35c0bf92dc084ab3356a56d2a9ca7d6 Mon Sep 17 00:00:00 2001 From: Igor Karpukhin Date: Wed, 20 Aug 2025 15:02:47 +0200 Subject: [PATCH 4/7] newReconcileContext renamed to newReconcileRequest --- .../atlasorgsettings/atlasorgsettings_controller_test.go | 2 +- internal/controller/atlasorgsettings/handler.go | 4 ++-- internal/controller/atlasorgsettings/handler_test.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/controller/atlasorgsettings/atlasorgsettings_controller_test.go b/internal/controller/atlasorgsettings/atlasorgsettings_controller_test.go index 213b88c04d..99bdf85935 100644 --- a/internal/controller/atlasorgsettings/atlasorgsettings_controller_test.go +++ b/internal/controller/atlasorgsettings/atlasorgsettings_controller_test.go @@ -233,7 +233,7 @@ func TestAtlasOrgSettingsHandler_NewReconcileContext(t *testing.T) { } ctx := context.Background() - req, err := handler.newReconcileContext(ctx, orgSettings) + req, err := handler.newReconcileRequest(ctx, orgSettings) require.NoError(t, err) assert.NotNil(t, req) diff --git a/internal/controller/atlasorgsettings/handler.go b/internal/controller/atlasorgsettings/handler.go index 89e43a2b55..8e1c5420e6 100644 --- a/internal/controller/atlasorgsettings/handler.go +++ b/internal/controller/atlasorgsettings/handler.go @@ -33,7 +33,7 @@ type reconcileRequest struct { aos *akov2.AtlasOrgSettings } -func (h *AtlasOrgSettingsHandler) newReconcileContext(ctx context.Context, aos *akov2.AtlasOrgSettings) (*reconcileRequest, error) { +func (h *AtlasOrgSettingsHandler) newReconcileRequest(ctx context.Context, aos *akov2.AtlasOrgSettings) (*reconcileRequest, error) { var objKey *client.ObjectKey if aos.Spec.ConnectionSecretRef != nil && aos.Spec.ConnectionSecretRef.Name != "" { objKey = &client.ObjectKey{ @@ -59,7 +59,7 @@ func (h *AtlasOrgSettingsHandler) newReconcileContext(ctx context.Context, aos * func (h *AtlasOrgSettingsHandler) upsert(ctx context.Context, currentState, nextState state.ResourceState, aos *akov2.AtlasOrgSettings) (ctrlstate.Result, error) { - reconcileCtx, err := h.newReconcileContext(ctx, aos) + reconcileCtx, err := h.newReconcileRequest(ctx, aos) if err != nil { return result.Error(currentState, fmt.Errorf("failed to create reconcile context: %w", err)) } diff --git a/internal/controller/atlasorgsettings/handler_test.go b/internal/controller/atlasorgsettings/handler_test.go index f04caa7d96..b076c0259d 100644 --- a/internal/controller/atlasorgsettings/handler_test.go +++ b/internal/controller/atlasorgsettings/handler_test.go @@ -299,7 +299,7 @@ func TestNewReconcileContext(t *testing.T) { serviceBuilder: tt.serviceBuilder, } - reconcileCtx, err := h.newReconcileContext(ctx, tt.input) + reconcileCtx, err := h.newReconcileRequest(ctx, tt.input) if tt.wantErr != "" { assert.ErrorContains(t, err, tt.wantErr) assert.Nil(t, reconcileCtx) From 96dd9dd7ac4449add1044ac7b086e3ca1f7ff34c Mon Sep 17 00:00:00 2001 From: Igor Karpukhin Date: Wed, 20 Aug 2025 15:08:04 +0200 Subject: [PATCH 5/7] Removed deletionProtection for AtlasOrgSettings --- .../atlasorgsettings_controller.go | 5 +--- .../atlasorgsettings_controller_test.go | 29 ------------------- internal/controller/registry.go | 2 +- 3 files changed, 2 insertions(+), 34 deletions(-) diff --git a/internal/controller/atlasorgsettings/atlasorgsettings_controller.go b/internal/controller/atlasorgsettings/atlasorgsettings_controller.go index 73241ecf3e..369d8bd8d8 100644 --- a/internal/controller/atlasorgsettings/atlasorgsettings_controller.go +++ b/internal/controller/atlasorgsettings/atlasorgsettings_controller.go @@ -49,8 +49,7 @@ type serviceBuilderFunc func(*atlas.ClientSet) atlasorgsettings.AtlasOrgSettings type AtlasOrgSettingsHandler struct { ctrlstate.StateHandler[akov2.AtlasOrgSettings] reconciler.AtlasReconciler - deletionProtection bool - serviceBuilder serviceBuilderFunc + serviceBuilder serviceBuilderFunc } func NewAtlasOrgSettingsReconciler( @@ -58,7 +57,6 @@ func NewAtlasOrgSettingsReconciler( atlasProvider atlas.Provider, logger *zap.Logger, globalSecretRef client.ObjectKey, - deletionProtection bool, reapplySupport bool, ) *ctrlstate.Reconciler[akov2.AtlasOrgSettings] { orgSettingsHandler := &AtlasOrgSettingsHandler{ @@ -68,7 +66,6 @@ func NewAtlasOrgSettingsReconciler( Log: logger.Named("controllers").Named("AtlasOrgSettings").Sugar(), GlobalSecretRef: globalSecretRef, }, - deletionProtection: deletionProtection, serviceBuilder: func(clientSet *atlas.ClientSet) atlasorgsettings.AtlasOrgSettingsService { return atlasorgsettings.NewAtlasOrgSettingsService(clientSet.SdkClient20250312006.OrganizationsApi) }, diff --git a/internal/controller/atlasorgsettings/atlasorgsettings_controller_test.go b/internal/controller/atlasorgsettings/atlasorgsettings_controller_test.go index 99bdf85935..0ef5a0c57c 100644 --- a/internal/controller/atlasorgsettings/atlasorgsettings_controller_test.go +++ b/internal/controller/atlasorgsettings/atlasorgsettings_controller_test.go @@ -68,7 +68,6 @@ func TestNewAtlasOrgSettingsReconciler(t *testing.T) { atlasProvider, logger, client.ObjectKey{Name: globalSecretRef.Name, Namespace: globalSecretRef.Namespace}, - true, false, ) @@ -101,7 +100,6 @@ func TestSetupWithManager(t *testing.T) { Log: &zap.SugaredLogger{}, GlobalSecretRef: client.ObjectKey{}, }, - deletionProtection: false, serviceBuilder: func(clientSet *atlas.ClientSet) atlasorgsettings.AtlasOrgSettingsService { return nil }, @@ -226,7 +224,6 @@ func TestAtlasOrgSettingsHandler_NewReconcileContext(t *testing.T) { }, }, }, - deletionProtection: false, serviceBuilder: func(clientSet *atlas.ClientSet) atlasorgsettings.AtlasOrgSettingsService { return nil }, @@ -252,32 +249,6 @@ func TestAtlasOrgSettingsHandler_ServiceBuilder(t *testing.T) { assert.Nil(t, service) } -func TestAtlasOrgSettingsHandler_DeletionProtection(t *testing.T) { - testCases := []struct { - name string - deletionProtection bool - }{ - { - name: "deletion protection enabled", - deletionProtection: true, - }, - { - name: "deletion protection disabled", - deletionProtection: false, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - handler := &AtlasOrgSettingsHandler{ - deletionProtection: tc.deletionProtection, - } - - assert.Equal(t, tc.deletionProtection, handler.deletionProtection) - }) - } -} - type fakeManager struct { controllerruntime.Manager client client.Client diff --git a/internal/controller/registry.go b/internal/controller/registry.go index 631e25e768..2f611ed8cd 100644 --- a/internal/controller/registry.go +++ b/internal/controller/registry.go @@ -127,7 +127,7 @@ func (r *Registry) registerControllers(c cluster.Cluster, ap atlas.Provider) { reconcilers = append(reconcilers, atlasnetworkcontainer.NewAtlasNetworkContainerReconciler(c, r.defaultPredicates(), ap, r.deletionProtection, r.logger, r.independentSyncPeriod, r.globalSecretRef)) reconcilers = append(reconcilers, atlasnetworkpeering.NewAtlasNetworkPeeringsReconciler(c, r.defaultPredicates(), ap, r.deletionProtection, r.logger, r.independentSyncPeriod, r.globalSecretRef)) - orgSettingsReconciler := atlasorgsettings.NewAtlasOrgSettingsReconciler(c, ap, r.logger, r.globalSecretRef, r.deletionProtection, r.reapplySupport) + orgSettingsReconciler := atlasorgsettings.NewAtlasOrgSettingsReconciler(c, ap, r.logger, r.globalSecretRef, r.reapplySupport) reconcilers = append(reconcilers, newCtrlStateReconciler(orgSettingsReconciler)) integrationsReconciler := integrations.NewAtlasThirdPartyIntegrationsReconciler(c, ap, r.deletionProtection, r.logger, r.globalSecretRef, r.reapplySupport) reconcilers = append(reconcilers, newCtrlStateReconciler(integrationsReconciler)) From 38a55a4db696a6d11ec7e9cb302ca6022c0c5af1 Mon Sep 17 00:00:00 2001 From: Igor Karpukhin Date: Wed, 20 Aug 2025 21:50:24 +0200 Subject: [PATCH 6/7] Added e2e test for AtlasOrgSettings --- .github/workflows/test-e2e.yml | 1 + test/e2e/atlasorgsettings_test.go | 188 ++++++++++++++++++++++++++++++ test/helper/e2e/actions/steps.go | 13 +++ test/helper/e2e/k8s/k8s.go | 9 ++ 4 files changed, 211 insertions(+) create mode 100644 test/e2e/atlasorgsettings_test.go diff --git a/.github/workflows/test-e2e.yml b/.github/workflows/test-e2e.yml index 5764f3ea21..5ab452d2dd 100644 --- a/.github/workflows/test-e2e.yml +++ b/.github/workflows/test-e2e.yml @@ -109,6 +109,7 @@ jobs: "dry-run", "networkcontainer-controller", "networkpeering-controller", + "atlas-org-settings", ] steps: - uses: actions/checkout@v5 diff --git a/test/e2e/atlasorgsettings_test.go b/test/e2e/atlasorgsettings_test.go new file mode 100644 index 0000000000..f6c7bf272c --- /dev/null +++ b/test/e2e/atlasorgsettings_test.go @@ -0,0 +1,188 @@ +// 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 e2e_test + +import ( + "os" + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + "github.com/mongodb/mongodb-atlas-kubernetes/v2/api" + akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/pointer" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e/actions" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e/data" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e/model" + "github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/e2e/utils" +) + +var _ = Describe("AtlasOrgSettings", Label("atlas-org-settings"), func() { + var testData *model.TestDataProvider + + _ = AfterEach(func() { + GinkgoWriter.Write([]byte("\n")) + GinkgoWriter.Write([]byte("===============================================\n")) + GinkgoWriter.Write([]byte("AtlasOrgSettings Test\n")) + GinkgoWriter.Write([]byte("Operator namespace: " + testData.Resources.Namespace + "\n")) + GinkgoWriter.Write([]byte("===============================================\n")) + if CurrentSpecReport().Failed() { + Expect(actions.SaveProjectsToFile(testData.Context, testData.K8SClient, testData.Resources.Namespace)).Should(Succeed()) + Expect(actions.SaveAtlasOrgSettingsToFile(testData.Context, testData.K8SClient, testData.Resources.Namespace)).Should(Succeed()) + } + By("Delete Resources", func() { + actions.DeleteTestDataProject(testData) + actions.AfterEachFinalCleanup([]model.TestDataProvider{*testData}) + }) + }) + + It("Should create AtlasOrgSettings with strict configuration and verify its status", func() { + testData = model.DataProvider( + "atlas-org-settings", + model.NewEmptyAtlasKeyType().UseDefaultFullAccess(), + 40000, + []func(*model.TestDataProvider){}, + ).WithProject(data.DefaultProject()) + + actions.ProjectCreationFlow(testData) + + orgSettings := akov2.AtlasOrgSettings{ + ObjectMeta: metav1.ObjectMeta{ + Name: utils.RandomName("org-settings-strict"), + }, + Spec: akov2.AtlasOrgSettingsSpec{ + OrgID: "", + ConnectionSecretRef: &api.LocalObjectReference{ + Name: "my-atlas-key", + }, + ApiAccessListRequired: pointer.MakePtr(true), + GenAIFeaturesEnabled: pointer.MakePtr(false), + MultiFactorAuthRequired: pointer.MakePtr(true), + RestrictEmployeeAccess: pointer.MakePtr(true), + SecurityContact: pointer.MakePtr("security@example.com"), + MaxServiceAccountSecretValidityInHours: pointer.MakePtr(24), + }, + } + + By("Set the organization ID from environment", func() { + orgSettings.Spec.OrgID = os.Getenv("MCLI_ORG_ID") + Expect(orgSettings.Spec.OrgID).ShouldNot(BeEmpty(), "MCLI_ORG_ID environment variable must be set") + }) + + By("Create AtlasOrgSettings", func() { + orgSettings.Namespace = testData.Resources.Namespace + Expect(testData.K8SClient.Create(testData.Context, &orgSettings)).Should(Succeed()) + }) + + By("Wait for AtlasOrgSettings to be ready", func() { + Eventually(func(g Gomega) bool { + return atlasOrgSettingsIsReady(g, testData, orgSettings.Name) + }).WithTimeout(10 * time.Minute).WithPolling(20 * time.Second).Should(BeTrue()) + }) + + By("Check AtlasOrgSettings status conditions", func() { + Eventually(func(g Gomega) bool { + currentOrgSettings := &akov2.AtlasOrgSettings{} + err := testData.K8SClient.Get( + testData.Context, + types.NamespacedName{Name: orgSettings.Name, Namespace: testData.Resources.Namespace}, + currentOrgSettings, + ) + g.Expect(err).ShouldNot(HaveOccurred()) + + for _, condition := range currentOrgSettings.Status.Conditions { + if condition.Type == string(api.ReadyType) && condition.Status == metav1.ConditionTrue { + return true + } + } + return false + }).WithTimeout(10 * time.Minute).WithPolling(20 * time.Second).Should(BeTrue()) + }) + + By("Verify AtlasOrgSettings spec fields are preserved", func() { + currentOrgSettings := &akov2.AtlasOrgSettings{} + Expect(testData.K8SClient.Get( + testData.Context, + types.NamespacedName{Name: orgSettings.Name, Namespace: testData.Resources.Namespace}, + currentOrgSettings, + )).Should(Succeed()) + + Expect(currentOrgSettings.Spec.OrgID).Should(Equal(orgSettings.Spec.OrgID)) + Expect(currentOrgSettings.Spec.ConnectionSecretRef).Should(Equal(orgSettings.Spec.ConnectionSecretRef)) + + if orgSettings.Spec.ApiAccessListRequired != nil { + Expect(currentOrgSettings.Spec.ApiAccessListRequired).Should(Equal(orgSettings.Spec.ApiAccessListRequired)) + } + if orgSettings.Spec.GenAIFeaturesEnabled != nil { + Expect(currentOrgSettings.Spec.GenAIFeaturesEnabled).Should(Equal(orgSettings.Spec.GenAIFeaturesEnabled)) + } + if orgSettings.Spec.SecurityContact != nil { + Expect(currentOrgSettings.Spec.SecurityContact).Should(Equal(orgSettings.Spec.SecurityContact)) + } + }) + + By("Delete AtlasOrgSettings", func() { + currentOrgSettings := &akov2.AtlasOrgSettings{} + Expect(testData.K8SClient.Get( + testData.Context, + types.NamespacedName{Name: orgSettings.Name, Namespace: testData.Resources.Namespace}, + currentOrgSettings, + )).Should(Succeed()) + + Expect(testData.K8SClient.Delete(testData.Context, currentOrgSettings)).Should(Succeed()) + }) + + By("Wait for AtlasOrgSettings to be deleted", func() { + Eventually(func(g Gomega) bool { + currentOrgSettings := &akov2.AtlasOrgSettings{} + err := testData.K8SClient.Get( + testData.Context, + types.NamespacedName{Name: orgSettings.Name, Namespace: testData.Resources.Namespace}, + currentOrgSettings, + ) + return err != nil // Should return not found error + }).WithTimeout(5 * time.Minute).WithPolling(10 * time.Second).Should(BeTrue()) + }) + }) +}) + +func atlasOrgSettingsIsReady(g Gomega, userData *model.TestDataProvider, orgSettingsName string) bool { + currentOrgSettings := &akov2.AtlasOrgSettings{} + err := userData.K8SClient.Get( + userData.Context, + types.NamespacedName{Name: orgSettingsName, Namespace: userData.Resources.Namespace}, + currentOrgSettings, + ) + g.Expect(err).ShouldNot(HaveOccurred()) + + if currentOrgSettings.ResourceVersion == "" { + return false + } + + if len(currentOrgSettings.Status.Conditions) == 0 { + return false + } + + for _, condition := range currentOrgSettings.Status.Conditions { + if condition.Type == string(api.ReadyType) { + return condition.Status == metav1.ConditionTrue + } + } + + return false +} diff --git a/test/helper/e2e/actions/steps.go b/test/helper/e2e/actions/steps.go index 606b5698ff..011b285fc2 100644 --- a/test/helper/e2e/actions/steps.go +++ b/test/helper/e2e/actions/steps.go @@ -242,6 +242,19 @@ func SaveTeamsToFile(ctx context.Context, k8sClient client.Client, ns string) er return nil } +func SaveAtlasOrgSettingsToFile(ctx context.Context, k8sClient client.Client, ns string) error { + yaml, err := k8s.AtlasOrgSettingsListYaml(ctx, k8sClient, ns) + if err != nil { + return fmt.Errorf("error getting AtlasOrgSettings list: %w", err) + } + path := fmt.Sprintf("output/%s/%s.yaml", ns, "atlasorgsettings") + err = utils.SaveToFile(path, yaml) + if err != nil { + return fmt.Errorf("error saving AtlasOrgSettings to file: %w", err) + } + return nil +} + func SaveDeploymentsToFile(ctx context.Context, k8sClient client.Client, ns string) error { yaml, err := k8s.DeploymentListYml(ctx, k8sClient, ns) if err != nil { diff --git a/test/helper/e2e/k8s/k8s.go b/test/helper/e2e/k8s/k8s.go index 39248af498..d86df2d67e 100644 --- a/test/helper/e2e/k8s/k8s.go +++ b/test/helper/e2e/k8s/k8s.go @@ -364,6 +364,15 @@ func TeamListYaml(ctx context.Context, k8sClient client.Client, ns string) ([]by return yaml.Marshal(teamList) } +func AtlasOrgSettingsListYaml(ctx context.Context, k8sClient client.Client, ns string) ([]byte, error) { + orgSettingsList := &akov2.AtlasOrgSettingsList{} + err := k8sClient.List(ctx, orgSettingsList, client.InNamespace(ns)) + if err != nil { + return nil, err + } + return yaml.Marshal(orgSettingsList) +} + func DeploymentListYml(ctx context.Context, k8sClient client.Client, ns string) ([]byte, error) { deploymentList := &akov2.AtlasDeploymentList{} err := k8sClient.List(ctx, deploymentList, client.InNamespace(ns)) From 0c666bb7be813ea4fd47ad324f3c78271748c6ba Mon Sep 17 00:00:00 2001 From: Igor Karpukhin Date: Thu, 21 Aug 2025 09:28:31 +0200 Subject: [PATCH 7/7] Fixed credentials for test --- test/e2e/atlasorgsettings_test.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/e2e/atlasorgsettings_test.go b/test/e2e/atlasorgsettings_test.go index f6c7bf272c..33da5a4829 100644 --- a/test/e2e/atlasorgsettings_test.go +++ b/test/e2e/atlasorgsettings_test.go @@ -66,10 +66,7 @@ var _ = Describe("AtlasOrgSettings", Label("atlas-org-settings"), func() { Name: utils.RandomName("org-settings-strict"), }, Spec: akov2.AtlasOrgSettingsSpec{ - OrgID: "", - ConnectionSecretRef: &api.LocalObjectReference{ - Name: "my-atlas-key", - }, + OrgID: "", ApiAccessListRequired: pointer.MakePtr(true), GenAIFeaturesEnabled: pointer.MakePtr(false), MultiFactorAuthRequired: pointer.MakePtr(true),