Skip to content

Commit a6b98f6

Browse files
committed
CLOUDP-305560: Add Atlas Integration CRD
Signed-off-by: jose.vazquez <[email protected]>
1 parent dac939c commit a6b98f6

13 files changed

+1062
-0
lines changed

PROJECT

+8
Original file line numberDiff line numberDiff line change
@@ -143,4 +143,12 @@ resources:
143143
kind: AtlasNetworkPeering
144144
path: github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1
145145
version: v1
146+
- api:
147+
crdVersion: v1
148+
namespaced: true
149+
domain: mongodb.com
150+
group: atlas
151+
kind: AtlasIntegration
152+
path: github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1
153+
version: v1
146154
version: "3"

api/v1/atlasintegration_types.go

+187
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
/*
2+
Copyright 2025 MongoDB.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package v1
18+
19+
import (
20+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
22+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1/status"
23+
)
24+
25+
func init() {
26+
SchemeBuilder.Register(&AtlasIntegration{}, &AtlasIntegrationList{})
27+
}
28+
29+
// +kubebuilder:object:root=true
30+
31+
// AtlasIntegration is the Schema for the atlas 3rd party inegrations API.
32+
type AtlasIntegration struct {
33+
metav1.TypeMeta `json:",inline"`
34+
metav1.ObjectMeta `json:"metadata,omitempty"`
35+
36+
Spec AtlasIntegrationSpec `json:"spec,omitempty"`
37+
Status status.AtlasIntegrationStatus `json:"status,omitempty"`
38+
}
39+
40+
// AtlasIntegrationSpec contains the expected configuration for an integration
41+
// +kubebuilder:validation:XValidation:rule="(has(self.externalProjectRef) && !has(self.projectRef)) || (!has(self.externalProjectRef) && has(self.projectRef))",message="must define only one project reference through externalProjectRef or projectRef"
42+
// +kubebuilder:validation:XValidation:rule="(has(self.externalProjectRef) && has(self.connectionSecret)) || !has(self.externalProjectRef)",message="must define a local connection secret when referencing an external project"
43+
// +kubebuilder:validation:XValidation:rule="has(self.type) && self.type.size() != 0",message="must define a type of integration"
44+
// +kubebuilder:validation:XValidation:rule="!has(self.datadog) || (self.type == 'DATADOG' && has(self.datadog))",message="only DATADOG type may set datadog fields"
45+
// +kubebuilder:validation:XValidation:rule="!has(self.microsoftTeams) || (self.type == 'MICROSOFT_TEAMS' && has(self.microsoftTeams))",message="only MICROSOFT_TEAMS type may set microsoftTeams fields"
46+
// +kubebuilder:validation:XValidation:rule="!has(self.newRelic) || (self.type == 'NEW_RELIC' && has(self.newRelic))",message="only NEW_RELIC type may set newRelic fields"
47+
// +kubebuilder:validation:XValidation:rule="!has(self.opsGenie) || (self.type == 'OPS_GENIE' && has(self.opsGenie))",message="only OPS_GENIE type may set opsGenie fields"
48+
// +kubebuilder:validation:XValidation:rule="!has(self.prometheus) || (self.type == 'PROMETHEUS' && has(self.prometheus))",message="only PROMETHEUS type may set prometheus fields"
49+
// +kubebuilder:validation:XValidation:rule="!has(self.pagerDuty) || (self.type == 'PAGER_DUTY' && has(self.pagerDuty))",message="only PAGER_DUTY type may set pagerDuty fields"
50+
// +kubebuilder:validation:XValidation:rule="!has(self.slack) || (self.type == 'SLACK' && has(self.slack))",message="only SLACK type may set slack fields"
51+
// +kubebuilder:validation:XValidation:rule="!has(self.victorOps) || (self.type == 'VICTOR_OPS' && has(self.victorOps))",message="only VICTOR_OPS type may set victorOps fields"
52+
// +kubebuilder:validation:XValidation:rule="!has(self.webhook) || (self.type == 'WEBHOOK' && has(self.webhook))",message="only WEBHOOK type may set webhook fields"
53+
type AtlasIntegrationSpec struct {
54+
ProjectDualReference `json:",inline"`
55+
56+
// ID of the integration in Atlas. May be omitted to create a new one.
57+
// +kubebuilder:validation:Optional
58+
ID *string `json:"id"`
59+
60+
// Type of the integration
61+
// +kubebuilder:validation:Enum:=DATADOG;MICROSOFT_TEAMS;NEW_RELIC;OPS_GENIE;PAGER_DUTY;PROMETHEUS;SLACK;VICTOR_OPS;WEBHOOK
62+
// +kubebuilder:validation:Required
63+
Type string `json:"type"`
64+
65+
Datadog *DatadogIntegration `json:"datadog,omitempty"`
66+
67+
MicrosoftTeams *MicrosoftTeamsIntegration `json:"microsoftTeams,omitempty"`
68+
69+
NewRelic *NewRelicIntegration `json:"newRelic,omitempty"`
70+
71+
OpsGenie *OpsGenieIntegration `json:"opsGenie,omitempty"`
72+
73+
PagerDuty *PagerDutyIntegration `json:"pagerDuty,omitempty"`
74+
75+
Prometheus *PrometheusIntegration `json:"prometheus,omitempty"`
76+
77+
Slack *SlackIntegration `json:"slack,omitempty"`
78+
79+
VictorOps *VictorOpsIntegration `json:"victorOps,omitempty"`
80+
81+
Webhook *WebhookIntegration `json:"webhook,omitempty"`
82+
}
83+
84+
type DatadogIntegration struct {
85+
// APIKeySecret is the name of a secret containing the datadog api key
86+
// +kubebuilder:validation:Required
87+
APIKeySecret string `json:"apiKeySecret"`
88+
89+
// Region is the Datadog region
90+
// +kubebuilder:validation:Required
91+
Region string `json:"region"`
92+
93+
// SendCollectionLatencyMetrics flags whether or not to send collection latency metrics
94+
// +kubebuilder:validation:Required
95+
SendCollectionLatencyMetrics bool `json:"sendCollectionLatencyMetrics"`
96+
97+
// SendDatabaseMetrics flags whether or not to send database metrics
98+
// +kubebuilder:validation:Required
99+
SendDatabaseMetrics bool `json:"sendDatabaseMetrics"`
100+
}
101+
102+
type MicrosoftTeamsIntegration struct {
103+
// URLSecret is the name of a secret containing the microsoft teams secret URL
104+
// +kubebuilder:validation:Required
105+
URLSecret string `json:"apiKeySecret"`
106+
}
107+
108+
type NewRelicIntegration struct {
109+
// CredentialsSecret is the name of a secret containing new relic's credentials:
110+
// account id, license key, read and write tokens
111+
// +kubebuilder:validation:Required
112+
CredentialsSecret string `json:"credentialsSecret"`
113+
}
114+
115+
type OpsGenieIntegration struct {
116+
// APIKeySecret is the name of a secret containing Ops Genie's API key
117+
// +kubebuilder:validation:Required
118+
APIKeySecret string `json:"apiKeySecret"`
119+
120+
// Region is the Ops Genie region
121+
// +kubebuilder:validation:Required
122+
Region string `json:"region"`
123+
}
124+
125+
type PagerDutyIntegration struct {
126+
// ServiceKeySecret is the name of a secret containing Pager Duty service key
127+
// +kubebuilder:validation:Required
128+
ServiceKeySecret string `json:"serviceKeySecret"`
129+
130+
// Region is the Pager Duty region
131+
// +kubebuilder:validation:Required
132+
Region string `json:"region"`
133+
}
134+
135+
type PrometheusIntegration struct {
136+
// UsernameSecret is the name of a secret containing the Prometehus username
137+
// +kubebuilder:validation:Required
138+
UsernameSecret string `json:"usernameSecret"`
139+
140+
// ServiceDiscovery to be used by Prometheus
141+
// +kubebuilder:validation:Enum:=file;http
142+
// +kubebuilder:validation:Required
143+
ServiceDiscovery string `json:"region"`
144+
145+
// Enabled flags whether or not Prometheus integration is enabled
146+
// +kubebuilder:validation:Required
147+
Enabled bool `json:"sendCollectionLatencyMetrics"`
148+
}
149+
150+
type SlackIntegration struct {
151+
// APITokenSecret is the name of a secret containing the Slack API token
152+
// +kubebuilder:validation:Required
153+
APITokenSecret string `json:"usernameSecret"`
154+
155+
// ChannelName to be used by Prometheus
156+
// +kubebuilder:validation:Required
157+
ChannelName string `json:"channelName"`
158+
159+
// TeamName flags whether or not Prometheus integration is enabled
160+
// +kubebuilder:validation:Required
161+
TeamName string `json:"teamName"`
162+
}
163+
164+
type VictorOpsIntegration struct {
165+
// KeysSecret is the name of a secret containing Victor Ops API and routing keys
166+
// +kubebuilder:validation:Required
167+
KeysSecret string `json:"keySecret"`
168+
}
169+
170+
type WebhookIntegration struct {
171+
// URLSecret is the name of a secret containing Webhook URL and secret
172+
// +kubebuilder:validation:Required
173+
URLSecret string `json:"keySecret"`
174+
}
175+
176+
// +kubebuilder:object:root=true
177+
178+
// AtlasIntegrationList contains a list of Atlas Integrations.
179+
type AtlasIntegrationList struct {
180+
metav1.TypeMeta `json:",inline"`
181+
metav1.ListMeta `json:"metadata,omitempty"`
182+
Items []AtlasIntegration `json:"items"`
183+
}
184+
185+
func (i *AtlasIntegration) ProjectDualRef() *ProjectDualReference {
186+
return &i.Spec.ProjectDualReference
187+
}

api/v1/atlasintegration_types_test.go

+181
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
package v1
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
"k8s.io/apimachinery/pkg/runtime"
9+
10+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1/common"
11+
"github.com/mongodb/mongodb-atlas-kubernetes/v2/test/helper/cel"
12+
)
13+
14+
func TestIntegrationCELChecks(t *testing.T) {
15+
for _, tc := range []struct {
16+
title string
17+
obj *AtlasIntegration
18+
expectedErrors []string
19+
}{
20+
{
21+
title: "fails with no type",
22+
obj: &AtlasIntegration{},
23+
expectedErrors: []string{"spec: Invalid value: \"object\": must define a type of integration"},
24+
},
25+
{
26+
title: "Datadog works",
27+
obj: &AtlasIntegration{
28+
Spec: AtlasIntegrationSpec{
29+
Type: "DATADOG",
30+
Datadog: &DatadogIntegration{
31+
APIKeySecret: "api-key-secretname",
32+
Region: "US",
33+
SendCollectionLatencyMetrics: false,
34+
SendDatabaseMetrics: false,
35+
},
36+
},
37+
},
38+
},
39+
{
40+
title: "Microsoft Teams works",
41+
obj: &AtlasIntegration{
42+
Spec: AtlasIntegrationSpec{
43+
Type: "MICROSOFT_TEAMS",
44+
MicrosoftTeams: &MicrosoftTeamsIntegration{
45+
URLSecret: "url-secretname",
46+
},
47+
},
48+
},
49+
},
50+
{
51+
title: "New Relic works",
52+
obj: &AtlasIntegration{
53+
Spec: AtlasIntegrationSpec{
54+
Type: "NEW_RELIC",
55+
NewRelic: &NewRelicIntegration{
56+
CredentialsSecret: "credentials-secretname",
57+
},
58+
},
59+
},
60+
},
61+
{
62+
title: "Ops Genie works",
63+
obj: &AtlasIntegration{
64+
Spec: AtlasIntegrationSpec{
65+
Type: "OPS_GENIE",
66+
OpsGenie: &OpsGenieIntegration{
67+
APIKeySecret: "api-key-secretname",
68+
Region: "US",
69+
},
70+
},
71+
},
72+
},
73+
{
74+
title: "Pager Duty works",
75+
obj: &AtlasIntegration{
76+
Spec: AtlasIntegrationSpec{
77+
Type: "PAGER_DUTY",
78+
PagerDuty: &PagerDutyIntegration{
79+
ServiceKeySecret: "service-key-secretname",
80+
Region: "US",
81+
},
82+
},
83+
},
84+
},
85+
{
86+
title: "Prometheus duty works",
87+
obj: &AtlasIntegration{
88+
Spec: AtlasIntegrationSpec{
89+
Type: "PROMETHEUS",
90+
Prometheus: &PrometheusIntegration{
91+
UsernameSecret: "username-secretname",
92+
ServiceDiscovery: "http",
93+
Enabled: false,
94+
},
95+
},
96+
},
97+
},
98+
{
99+
title: "Slack works",
100+
obj: &AtlasIntegration{
101+
Spec: AtlasIntegrationSpec{
102+
Type: "SLACK",
103+
Slack: &SlackIntegration{
104+
APITokenSecret: "api-tooken-secretname",
105+
ChannelName: "channel",
106+
TeamName: "team",
107+
},
108+
},
109+
},
110+
},
111+
{
112+
title: "Victor ops works",
113+
obj: &AtlasIntegration{
114+
Spec: AtlasIntegrationSpec{
115+
Type: "VICTOR_OPS",
116+
VictorOps: &VictorOpsIntegration{
117+
KeysSecret: "keys-secetname",
118+
},
119+
},
120+
},
121+
},
122+
{
123+
title: "Webhook works",
124+
obj: &AtlasIntegration{
125+
Spec: AtlasIntegrationSpec{
126+
Type: "WEBHOOK",
127+
Webhook: &WebhookIntegration{
128+
URLSecret: "url-secretname",
129+
},
130+
},
131+
},
132+
},
133+
{
134+
title: "Prometheus on Pager Duty type fails",
135+
obj: &AtlasIntegration{
136+
Spec: AtlasIntegrationSpec{
137+
Type: "PAGER_DUTY",
138+
PagerDuty: &PagerDutyIntegration{},
139+
Prometheus: &PrometheusIntegration{
140+
UsernameSecret: "username-secretname",
141+
ServiceDiscovery: "http",
142+
Enabled: false,
143+
},
144+
},
145+
},
146+
expectedErrors: []string{"spec: Invalid value: \"object\": only PROMETHEUS type may set prometheus fields"},
147+
},
148+
{
149+
title: "Datadog on Webhook type fails",
150+
obj: &AtlasIntegration{
151+
Spec: AtlasIntegrationSpec{
152+
Type: "WEBHOOK",
153+
Datadog: &DatadogIntegration{
154+
APIKeySecret: "api-key-secretname",
155+
Region: "US",
156+
SendCollectionLatencyMetrics: false,
157+
SendDatabaseMetrics: false,
158+
},
159+
Webhook: &WebhookIntegration{
160+
URLSecret: "url-secretname",
161+
},
162+
},
163+
},
164+
expectedErrors: []string{"spec: Invalid value: \"object\": only DATADOG type may set datadog fields"},
165+
},
166+
} {
167+
t.Run(tc.title, func(t *testing.T) {
168+
// inject a project to avoid other CEL validations being hit
169+
tc.obj.Spec.ProjectRef = &common.ResourceRefNamespaced{Name: "some-project"}
170+
unstructuredObject, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&tc.obj)
171+
require.NoError(t, err)
172+
173+
crdPath := "../../config/crd/bases/atlas.mongodb.com_atlasintegrations.yaml"
174+
validator, err := cel.VersionValidatorFromFile(t, crdPath, "v1")
175+
assert.NoError(t, err)
176+
errs := validator(unstructuredObject, nil)
177+
178+
require.Equal(t, tc.expectedErrors, cel.ErrorListAsStrings(errs))
179+
})
180+
}
181+
}

api/v1/project_reference_cel_test.go

+8
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ var dualRefCRDs = []struct {
6363
},
6464
filename: "atlas.mongodb.com_atlasnetworkpeerings.yaml",
6565
},
66+
{
67+
obj: &AtlasIntegration{
68+
Spec: AtlasIntegrationSpec{ // Avoid triggering integration specific validations
69+
Type: "DATADOG",
70+
},
71+
},
72+
filename: "atlas.mongodb.com_atlasintegrations.yaml",
73+
},
6674
}
6775

6876
var testCases = []struct {

0 commit comments

Comments
 (0)