Skip to content

Commit c75db21

Browse files
authored
CLOUDP-283291: Support export private endpoint custom resource (#3498)
1 parent 1ff4825 commit c75db21

15 files changed

+1636
-630
lines changed

docs/command/atlas-kubernetes-config-generate.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ Options
6060
* - --operatorVersion
6161
- string
6262
- false
63-
- Version of Atlas Kubernetes Operator to generate resources for. This value defaults to "2.5.0".
63+
- Version of Atlas Kubernetes Operator to generate resources for. This value defaults to "2.6.0".
6464
* - --orgId
6565
- string
6666
- false

go.mod

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ require (
3030
github.com/mholt/archives v0.0.0-20241207175349-5e373c52f8aa
3131
github.com/mongodb-forks/digest v1.1.0
3232
github.com/mongodb-labs/cobra2snooty v0.18.2
33-
github.com/mongodb/mongodb-atlas-kubernetes/v2 v2.5.0
33+
github.com/mongodb/mongodb-atlas-kubernetes/v2 v2.6.0
3434
github.com/pelletier/go-toml v1.9.5
3535
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
3636
github.com/shirou/gopsutil/v4 v4.24.11
@@ -56,11 +56,11 @@ require (
5656
google.golang.org/grpc v1.69.0
5757
google.golang.org/protobuf v1.36.0
5858
gopkg.in/yaml.v3 v3.0.1
59-
k8s.io/api v0.31.3
60-
k8s.io/apiextensions-apiserver v0.31.3
61-
k8s.io/apimachinery v0.31.3
62-
k8s.io/apiserver v0.31.3
63-
k8s.io/client-go v0.31.3
59+
k8s.io/api v0.32.0
60+
k8s.io/apiextensions-apiserver v0.32.0
61+
k8s.io/apimachinery v0.32.0
62+
k8s.io/apiserver v0.32.0
63+
k8s.io/client-go v0.32.0
6464
sigs.k8s.io/controller-runtime v0.19.3
6565
sigs.k8s.io/kind v0.25.0
6666
sigs.k8s.io/yaml v1.4.0
@@ -109,9 +109,9 @@ require (
109109
github.com/go-logr/logr v1.4.2 // indirect
110110
github.com/go-logr/stdr v1.2.2 // indirect
111111
github.com/go-ole/go-ole v1.2.6 // indirect
112-
github.com/go-openapi/jsonpointer v0.19.6 // indirect
112+
github.com/go-openapi/jsonpointer v0.21.0 // indirect
113113
github.com/go-openapi/jsonreference v0.20.2 // indirect
114-
github.com/go-openapi/swag v0.22.4 // indirect
114+
github.com/go-openapi/swag v0.23.0 // indirect
115115
github.com/gogo/protobuf v1.3.2 // indirect
116116
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
117117
github.com/golang/protobuf v1.5.4 // indirect
@@ -130,7 +130,6 @@ require (
130130
github.com/hashicorp/go-multierror v1.1.1 // indirect
131131
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
132132
github.com/hashicorp/hcl v1.0.0 // indirect
133-
github.com/imdario/mergo v0.3.12 // indirect
134133
github.com/inconshreveable/mousetrap v1.1.0 // indirect
135134
github.com/josharian/intern v1.0.0 // indirect
136135
github.com/json-iterator/go v1.1.12 // indirect
@@ -170,6 +169,7 @@ require (
170169
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
171170
github.com/yusufpapurcu/wmi v1.2.4 // indirect
172171
go.mongodb.org/atlas-sdk/v20231115008 v20231115008.5.0 // indirect
172+
go.mongodb.org/atlas-sdk/v20241113001 v20241113001.0.0 // indirect
173173
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
174174
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect
175175
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect
@@ -187,14 +187,13 @@ require (
187187
golang.org/x/text v0.21.0 // indirect
188188
golang.org/x/time v0.8.0 // indirect
189189
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect
190-
google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f // indirect
190+
google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 // indirect
191191
google.golang.org/genproto/googleapis/rpc v0.0.0-20241206012308-a4fef0638583 // indirect
192192
gopkg.in/inf.v0 v0.9.1 // indirect
193193
gopkg.in/ini.v1 v1.67.0 // indirect
194-
gopkg.in/yaml.v2 v2.4.0 // indirect
195194
k8s.io/klog/v2 v2.130.1 // indirect
196-
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
197-
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
198-
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
199-
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
195+
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
196+
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
197+
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
198+
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
200199
)

go.sum

Lines changed: 42 additions & 43 deletions
Large diffs are not rendered by default.

internal/kubernetes/operator/config_exporter.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,28 @@ func (e *ConfigExporter) exportProject() ([]runtime.Object, string, error) {
234234
e.dictionaryForAtlasNames,
235235
))
236236

237+
if e.featureValidator.IsResourceSupported(features.ResourceAtlasPrivateEndpoint) {
238+
privateEndpoints, err := project.BuildPrivateEndpointCustomResources(
239+
e.dataProvider,
240+
project.PrivateEndpointRequest{
241+
ProjectName: projectData.Project.Name,
242+
ProjectID: e.projectID,
243+
TargetNamespace: e.targetNamespace,
244+
Version: e.operatorVersion,
245+
Credentials: credentialsName,
246+
IndependentResource: e.independentResources,
247+
Dictionary: e.dictionaryForAtlasNames,
248+
},
249+
)
250+
if err != nil {
251+
return nil, "", err
252+
}
253+
254+
for _, privateEndpoint := range privateEndpoints {
255+
r = append(r, &privateEndpoint)
256+
}
257+
}
258+
237259
// DB users
238260
usersData, relatedSecrets, err := dbusers.BuildDBUsers(
239261
e.dataProvider,

internal/kubernetes/operator/features/crds.go

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import (
2727
)
2828

2929
const (
30-
LatestOperatorMajorVersion = "2.5.0"
30+
LatestOperatorMajorVersion = "2.6.0"
3131
maxDepth = 100
3232
ResourceVersion = "mongodb.com/atlas-resource-version"
3333
ResourceAtlasProject = "atlasprojects"
@@ -41,6 +41,7 @@ const (
4141
ResourceAtlasStreamInstance = "atlasstreaminstances"
4242
ResourceAtlasStreamConnection = "atlasstreamconnections"
4343
ResourceAtlasBackupCompliancePolicy = "atlasbackupcompliancepolicies"
44+
ResourceAtlasPrivateEndpoint = "atlasprivateendpoints"
4445
)
4546

4647
var (
@@ -52,17 +53,7 @@ var (
5253
ErrDocumentHasNoSpec = errors.New("document contains no Spec")
5354

5455
versionsToResourcesMap = map[string][]resource{
55-
"2.2.0": {
56-
resource{ResourceAtlasDatabaseUser, NopPatcher()},
57-
resource{ResourceAtlasProject, NopPatcher()},
58-
resource{ResourceAtlasDeployment, NopPatcher()},
59-
resource{ResourceAtlasBackupSchedule, NopPatcher()},
60-
resource{ResourceAtlasBackupPolicy, NopPatcher()},
61-
resource{ResourceAtlasTeam, NopPatcher()},
62-
resource{ResourceAtlasDataFederation, NopPatcher()},
63-
resource{ResourceAtlasFederatedAuth, NopPatcher()},
64-
},
65-
"2.3.0": {
56+
"2.4.0": {
6657
resource{ResourceAtlasDatabaseUser, NopPatcher()},
6758
resource{ResourceAtlasProject, NopPatcher()},
6859
resource{ResourceAtlasDeployment, NopPatcher()},
@@ -73,8 +64,9 @@ var (
7364
resource{ResourceAtlasFederatedAuth, NopPatcher()},
7465
resource{ResourceAtlasStreamInstance, NopPatcher()},
7566
resource{ResourceAtlasStreamConnection, NopPatcher()},
67+
resource{ResourceAtlasBackupCompliancePolicy, NopPatcher()},
7668
},
77-
"2.4.0": {
69+
"2.5.0": {
7870
resource{ResourceAtlasDatabaseUser, NopPatcher()},
7971
resource{ResourceAtlasProject, NopPatcher()},
8072
resource{ResourceAtlasDeployment, NopPatcher()},
@@ -87,7 +79,7 @@ var (
8779
resource{ResourceAtlasStreamConnection, NopPatcher()},
8880
resource{ResourceAtlasBackupCompliancePolicy, NopPatcher()},
8981
},
90-
"2.5.0": {
82+
"2.6.0": {
9183
resource{ResourceAtlasDatabaseUser, NopPatcher()},
9284
resource{ResourceAtlasProject, NopPatcher()},
9385
resource{ResourceAtlasDeployment, NopPatcher()},
@@ -99,6 +91,7 @@ var (
9991
resource{ResourceAtlasStreamInstance, NopPatcher()},
10092
resource{ResourceAtlasStreamConnection, NopPatcher()},
10193
resource{ResourceAtlasBackupCompliancePolicy, NopPatcher()},
94+
resource{ResourceAtlasPrivateEndpoint, NopPatcher()},
10295
},
10396
}
10497
)
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
// Copyright 2024 MongoDB Inc
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package project
16+
17+
import (
18+
"fmt"
19+
"strings"
20+
21+
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/kubernetes/operator/features"
22+
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/kubernetes/operator/resources"
23+
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/store"
24+
akoapi "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api"
25+
akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1"
26+
akov2common "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1/common"
27+
akov2status "github.com/mongodb/mongodb-atlas-kubernetes/v2/pkg/api/v1/status"
28+
atlasv2 "go.mongodb.org/atlas-sdk/v20241113004/admin"
29+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30+
)
31+
32+
type PrivateEndpointRequest struct {
33+
ProjectName string
34+
ProjectID string
35+
TargetNamespace string
36+
Version string
37+
Credentials string
38+
IndependentResource bool
39+
Dictionary map[string]string
40+
}
41+
42+
func BuildPrivateEndpointCustomResources(
43+
provider store.OperatorPrivateEndpointStore,
44+
request PrivateEndpointRequest,
45+
) ([]akov2.AtlasPrivateEndpoint, error) {
46+
services := make([]atlasv2.EndpointService, 0)
47+
48+
for _, cloud := range []string{"AWS", "AZURE", "GCP"} {
49+
cloudServices, err := provider.PrivateEndpoints(request.ProjectID, cloud)
50+
if err != nil {
51+
return nil, err
52+
}
53+
54+
services = append(services, cloudServices...)
55+
}
56+
57+
privateEndpoints := make([]akov2.AtlasPrivateEndpoint, 0, len(services))
58+
for _, service := range services {
59+
resourceName := resources.NormalizeAtlasName(
60+
fmt.Sprintf("%s-pe-%s-%s", request.ProjectName, service.GetCloudProvider(), strings.ToLower(strings.ReplaceAll(service.GetRegionName(), "_", ""))),
61+
request.Dictionary,
62+
)
63+
resource := akov2.AtlasPrivateEndpoint{
64+
TypeMeta: metav1.TypeMeta{
65+
Kind: "AtlasPrivateEndpoint",
66+
APIVersion: "atlas.mongodb.com/v1",
67+
},
68+
ObjectMeta: metav1.ObjectMeta{
69+
Name: resourceName,
70+
Namespace: request.TargetNamespace,
71+
Labels: map[string]string{
72+
features.ResourceVersion: request.Version,
73+
},
74+
},
75+
Spec: akov2.AtlasPrivateEndpointSpec{
76+
Provider: service.GetCloudProvider(),
77+
Region: service.GetRegionName(),
78+
},
79+
Status: akov2status.AtlasPrivateEndpointStatus{
80+
Common: akoapi.Common{
81+
Conditions: []akoapi.Condition{},
82+
},
83+
},
84+
}
85+
86+
if request.IndependentResource {
87+
resource.Spec.ExternalProject = &akov2.ExternalProjectReference{
88+
ID: request.ProjectID,
89+
}
90+
resource.Spec.LocalCredentialHolder = akoapi.LocalCredentialHolder{
91+
ConnectionSecret: &akoapi.LocalObjectReference{
92+
Name: resources.NormalizeAtlasName(request.Credentials, request.Dictionary),
93+
},
94+
}
95+
} else {
96+
resource.Spec.Project = &akov2common.ResourceRefNamespaced{
97+
Name: request.ProjectName,
98+
Namespace: request.TargetNamespace,
99+
}
100+
}
101+
102+
awsConfigs, err := buildAWSInterfaces(provider, request.ProjectID, service.GetId(), service.GetInterfaceEndpoints())
103+
if err != nil {
104+
return nil, err
105+
}
106+
resource.Spec.AWSConfiguration = awsConfigs
107+
108+
azureConfigs, err := buildAzureInterfaces(provider, request.ProjectID, service.GetId(), service.GetPrivateEndpoints())
109+
if err != nil {
110+
return nil, err
111+
}
112+
resource.Spec.AzureConfiguration = azureConfigs
113+
114+
gcpConfigs, err := buildGCPInterfaces(provider, request.ProjectID, service.GetId(), service.GetEndpointGroupNames())
115+
if err != nil {
116+
return nil, err
117+
}
118+
resource.Spec.GCPConfiguration = gcpConfigs
119+
120+
privateEndpoints = append(privateEndpoints, resource)
121+
}
122+
123+
return privateEndpoints, nil
124+
}
125+
126+
func buildAWSInterfaces(
127+
provider store.OperatorPrivateEndpointStore,
128+
projectID string,
129+
serviceID string,
130+
interfaceIDs []string,
131+
) ([]akov2.AWSPrivateEndpointConfiguration, error) {
132+
if len(interfaceIDs) == 0 {
133+
return nil, nil
134+
}
135+
136+
configs := make([]akov2.AWSPrivateEndpointConfiguration, 0, len(interfaceIDs))
137+
138+
for _, interfaceID := range interfaceIDs {
139+
pe, err := provider.InterfaceEndpoint(projectID, "AWS", serviceID, interfaceID)
140+
if err != nil {
141+
return nil, err
142+
}
143+
144+
configs = append(configs, akov2.AWSPrivateEndpointConfiguration{ID: pe.GetInterfaceEndpointId()})
145+
}
146+
147+
return configs, nil
148+
}
149+
150+
func buildAzureInterfaces(
151+
provider store.OperatorPrivateEndpointStore,
152+
projectID string,
153+
serviceID string,
154+
interfaceIDs []string,
155+
) ([]akov2.AzurePrivateEndpointConfiguration, error) {
156+
if len(interfaceIDs) == 0 {
157+
return nil, nil
158+
}
159+
160+
configs := make([]akov2.AzurePrivateEndpointConfiguration, 0, len(interfaceIDs))
161+
162+
for _, interfaceID := range interfaceIDs {
163+
pe, err := provider.InterfaceEndpoint(projectID, "AZURE", serviceID, interfaceID)
164+
if err != nil {
165+
return nil, err
166+
}
167+
168+
configs = append(
169+
configs,
170+
akov2.AzurePrivateEndpointConfiguration{
171+
ID: pe.GetPrivateEndpointResourceId(),
172+
IP: pe.GetPrivateEndpointIPAddress(),
173+
},
174+
)
175+
}
176+
177+
return configs, nil
178+
}
179+
180+
func buildGCPInterfaces(
181+
provider store.OperatorPrivateEndpointStore,
182+
projectID string,
183+
serviceID string,
184+
interfaceIDs []string,
185+
) ([]akov2.GCPPrivateEndpointConfiguration, error) {
186+
if len(interfaceIDs) == 0 {
187+
return nil, nil
188+
}
189+
190+
configs := make([]akov2.GCPPrivateEndpointConfiguration, 0, len(interfaceIDs))
191+
192+
for _, interfaceID := range interfaceIDs {
193+
pe, err := provider.InterfaceEndpoint(projectID, "GCP", serviceID, interfaceID)
194+
if err != nil {
195+
return nil, err
196+
}
197+
198+
gcpEPs := make([]akov2.GCPPrivateEndpoint, 0, len(pe.GetEndpoints()))
199+
for _, gcpEP := range pe.GetEndpoints() {
200+
gcpEPs = append(
201+
gcpEPs,
202+
akov2.GCPPrivateEndpoint{
203+
Name: gcpEP.GetEndpointName(),
204+
IP: gcpEP.GetIpAddress(),
205+
},
206+
)
207+
}
208+
209+
configs = append(
210+
configs,
211+
akov2.GCPPrivateEndpointConfiguration{
212+
// GCP ProjectID is not returned by Atlas and therefore, need to be set by the user after resource is exported
213+
ProjectID: "",
214+
GroupName: pe.GetEndpointGroupName(),
215+
Endpoints: gcpEPs,
216+
},
217+
)
218+
}
219+
220+
return configs, nil
221+
}

0 commit comments

Comments
 (0)