Skip to content

Commit a4fa2f1

Browse files
authored
CLOUDP-294105: remove kube lookups in Atlas Provider (#2158)
* remove kube lookups in atlas provider * refactor existing controllers * address review comments * fix nil value * remove v20231115008.SdkClient from workflow context, use client set only * remove sdk client in favor of client set * wire global secret in network peering controller
1 parent 9c31b0d commit a4fa2f1

File tree

82 files changed

+1368
-1356
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+1368
-1356
lines changed

internal/controller/atlas/provider.go

Lines changed: 31 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ import (
1313
adminv20241113001 "go.mongodb.org/atlas-sdk/v20241113001/admin"
1414
"go.mongodb.org/atlas/mongodbatlas"
1515
"go.uber.org/zap"
16-
corev1 "k8s.io/api/core/v1"
17-
"sigs.k8s.io/controller-runtime/pkg/client"
1816

1917
"github.com/mongodb/mongodb-atlas-kubernetes/v2/api"
2018
akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1"
@@ -25,14 +23,11 @@ import (
2523

2624
const (
2725
govAtlasDomain = "mongodbgov.com"
28-
orgIDKey = "orgId"
29-
publicAPIKey = "publicApiKey"
30-
privateAPIKey = "privateApiKey"
3126
)
3227

3328
type Provider interface {
34-
Client(ctx context.Context, secretRef *client.ObjectKey, log *zap.SugaredLogger) (*mongodbatlas.Client, string, error)
35-
SdkClientSet(ctx context.Context, secretRef *client.ObjectKey, log *zap.SugaredLogger) (*ClientSet, string, error)
29+
Client(ctx context.Context, creds *Credentials, log *zap.SugaredLogger) (*mongodbatlas.Client, error)
30+
SdkClientSet(ctx context.Context, creds *Credentials, log *zap.SugaredLogger) (*ClientSet, error)
3631
IsCloudGov() bool
3732
IsResourceSupported(resource api.AtlasCustomResource) bool
3833
}
@@ -43,24 +38,33 @@ type ClientSet struct {
4338
}
4439

4540
type ProductionProvider struct {
46-
k8sClient client.Client
47-
domain string
48-
globalSecretRef client.ObjectKey
49-
dryRun bool
41+
domain string
42+
dryRun bool
5043
}
5144

52-
type credentialsSecret struct {
53-
OrgID string
45+
// ConnectionConfig is the type that contains connection configuration to Atlas, including credentials.
46+
type ConnectionConfig struct {
47+
OrgID string
48+
Credentials *Credentials
49+
}
50+
51+
// Credentials is the type that holds credentials to authenticate against the Atlas API.
52+
// Currently, only API keys are support but more credential types could be added,
53+
// see https://www.mongodb.com/docs/atlas/configure-api-access/.
54+
type Credentials struct {
55+
APIKeys *APIKeys
56+
}
57+
58+
// APIKeys is the type that holds Public/Private API keys to authenticate against the Atlas API.
59+
type APIKeys struct {
5460
PublicKey string
5561
PrivateKey string
5662
}
5763

58-
func NewProductionProvider(atlasDomain string, globalSecretRef client.ObjectKey, k8sClient client.Client, dryRun bool) *ProductionProvider {
64+
func NewProductionProvider(atlasDomain string, dryRun bool) *ProductionProvider {
5965
return &ProductionProvider{
60-
k8sClient: k8sClient,
61-
domain: atlasDomain,
62-
globalSecretRef: globalSecretRef,
63-
dryRun: dryRun,
66+
domain: atlasDomain,
67+
dryRun: dryRun,
6468
}
6569
}
6670

@@ -102,35 +106,25 @@ func (p *ProductionProvider) IsResourceSupported(resource api.AtlasCustomResourc
102106
return false
103107
}
104108

105-
func (p *ProductionProvider) Client(ctx context.Context, secretRef *client.ObjectKey, log *zap.SugaredLogger) (*mongodbatlas.Client, string, error) {
106-
secretData, err := getSecrets(ctx, p.k8sClient, secretRef, &p.globalSecretRef)
107-
if err != nil {
108-
return nil, "", err
109-
}
110-
109+
func (p *ProductionProvider) Client(ctx context.Context, creds *Credentials, log *zap.SugaredLogger) (*mongodbatlas.Client, error) {
111110
clientCfg := []httputil.ClientOpt{
112-
httputil.Digest(secretData.PublicKey, secretData.PrivateKey),
111+
httputil.Digest(creds.APIKeys.PublicKey, creds.APIKeys.PrivateKey),
113112
httputil.LoggingTransport(log),
114113
}
115114

116115
transport := p.newDryRunTransport(http.DefaultTransport)
117116
httpClient, err := httputil.DecorateClient(&http.Client{Transport: transport}, clientCfg...)
118117
if err != nil {
119-
return nil, "", err
118+
return nil, err
120119
}
121120

122121
c, err := mongodbatlas.New(httpClient, mongodbatlas.SetBaseURL(p.domain), mongodbatlas.SetUserAgent(operatorUserAgent()))
123122

124-
return c, secretData.OrgID, err
123+
return c, err
125124
}
126125

127-
func (p *ProductionProvider) SdkClientSet(ctx context.Context, secretRef *client.ObjectKey, log *zap.SugaredLogger) (*ClientSet, string, error) {
128-
secretData, err := getSecrets(ctx, p.k8sClient, secretRef, &p.globalSecretRef)
129-
if err != nil {
130-
return nil, "", err
131-
}
132-
133-
var transport http.RoundTripper = digest.NewTransport(secretData.PublicKey, secretData.PrivateKey)
126+
func (p *ProductionProvider) SdkClientSet(ctx context.Context, creds *Credentials, log *zap.SugaredLogger) (*ClientSet, error) {
127+
var transport http.RoundTripper = digest.NewTransport(creds.APIKeys.PublicKey, creds.APIKeys.PrivateKey)
134128
transport = p.newDryRunTransport(transport)
135129
transport = httputil.NewLoggingTransport(log, false, transport)
136130

@@ -141,21 +135,21 @@ func (p *ProductionProvider) SdkClientSet(ctx context.Context, secretRef *client
141135
adminv20231115008.UseHTTPClient(httpClient),
142136
adminv20231115008.UseUserAgent(operatorUserAgent()))
143137
if err != nil {
144-
return nil, "", err
138+
return nil, err
145139
}
146140

147141
clientv20241113001, err := adminv20241113001.NewClient(
148142
adminv20241113001.UseBaseURL(p.domain),
149143
adminv20241113001.UseHTTPClient(httpClient),
150144
adminv20241113001.UseUserAgent(operatorUserAgent()))
151145
if err != nil {
152-
return nil, "", err
146+
return nil, err
153147
}
154148

155149
return &ClientSet{
156150
SdkClient20231115008: clientv20231115008,
157151
SdkClient20241113001: clientv20241113001,
158-
}, secretData.OrgID, nil
152+
}, nil
159153
}
160154

161155
func (p *ProductionProvider) newDryRunTransport(delegate http.RoundTripper) http.RoundTripper {
@@ -166,51 +160,6 @@ func (p *ProductionProvider) newDryRunTransport(delegate http.RoundTripper) http
166160
return delegate
167161
}
168162

169-
func getSecrets(ctx context.Context, k8sClient client.Client, secretRef, fallbackRef *client.ObjectKey) (*credentialsSecret, error) {
170-
if secretRef == nil {
171-
secretRef = fallbackRef
172-
}
173-
174-
secret := &corev1.Secret{}
175-
if err := k8sClient.Get(ctx, *secretRef, secret); err != nil {
176-
return nil, fmt.Errorf("failed to read Atlas API credentials from the secret %s: %w", secretRef.String(), err)
177-
}
178-
179-
secretData := credentialsSecret{
180-
OrgID: string(secret.Data[orgIDKey]),
181-
PublicKey: string(secret.Data[publicAPIKey]),
182-
PrivateKey: string(secret.Data[privateAPIKey]),
183-
}
184-
185-
if missingFields, valid := validateSecretData(&secretData); !valid {
186-
return nil, fmt.Errorf("the following fields are missing in the secret %v: %v", secretRef, missingFields)
187-
}
188-
189-
return &secretData, nil
190-
}
191-
192-
func validateSecretData(secretData *credentialsSecret) ([]string, bool) {
193-
missingFields := make([]string, 0, 3)
194-
195-
if secretData.OrgID == "" {
196-
missingFields = append(missingFields, orgIDKey)
197-
}
198-
199-
if secretData.PublicKey == "" {
200-
missingFields = append(missingFields, publicAPIKey)
201-
}
202-
203-
if secretData.PrivateKey == "" {
204-
missingFields = append(missingFields, privateAPIKey)
205-
}
206-
207-
if len(missingFields) > 0 {
208-
return missingFields, false
209-
}
210-
211-
return nil, true
212-
}
213-
214163
func operatorUserAgent() string {
215164
return fmt.Sprintf("%s/%s (%s;%s)", "MongoDBAtlasKubernetesOperator", version.Version, runtime.GOOS, runtime.GOARCH)
216165
}

internal/controller/atlas/provider_test.go

Lines changed: 4 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,29 @@
11
package atlas
22

33
import (
4-
"context"
54
"testing"
65

76
"github.com/stretchr/testify/assert"
87
"github.com/stretchr/testify/require"
9-
"go.uber.org/zap/zaptest"
10-
corev1 "k8s.io/api/core/v1"
11-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12-
"k8s.io/apimachinery/pkg/runtime"
13-
"sigs.k8s.io/controller-runtime/pkg/client"
14-
"sigs.k8s.io/controller-runtime/pkg/client/fake"
158

169
"github.com/mongodb/mongodb-atlas-kubernetes/v2/api"
1710
akov2 "github.com/mongodb/mongodb-atlas-kubernetes/v2/api/v1"
1811
"github.com/mongodb/mongodb-atlas-kubernetes/v2/internal/version"
1912
)
2013

21-
func TestProvider_Client(t *testing.T) {
22-
secret := &corev1.Secret{
23-
ObjectMeta: metav1.ObjectMeta{
24-
Name: "api-secret",
25-
Namespace: "default",
26-
Labels: map[string]string{
27-
"atlas.mongodb.com/type": "credentials",
28-
},
29-
},
30-
Data: map[string][]byte{
31-
"orgId": []byte("1234567890"),
32-
"publicApiKey": []byte("a1b2c3"),
33-
"privateApiKey": []byte("abcdef123456"),
34-
},
35-
Type: "Opaque",
36-
}
37-
38-
sch := runtime.NewScheme()
39-
sch.AddKnownTypes(corev1.SchemeGroupVersion, &corev1.Secret{})
40-
k8sClient := fake.NewClientBuilder().
41-
WithScheme(sch).
42-
WithObjects(secret).
43-
Build()
44-
45-
t.Run("should return Atlas API client and organization id using global secret", func(t *testing.T) {
46-
p := NewProductionProvider("https://cloud.mongodb.com/", client.ObjectKey{Name: "api-secret", Namespace: "default"}, k8sClient, false)
47-
48-
c, id, err := p.Client(context.Background(), nil, zaptest.NewLogger(t).Sugar())
49-
assert.NoError(t, err)
50-
assert.Equal(t, "1234567890", id)
51-
assert.NotNil(t, c)
52-
})
53-
54-
t.Run("should return Atlas API client and organization id using connection secret", func(t *testing.T) {
55-
p := NewProductionProvider("https://cloud.mongodb.com/", client.ObjectKey{Name: "global-secret", Namespace: "default"}, k8sClient, false)
56-
57-
c, id, err := p.Client(context.Background(), &client.ObjectKey{Name: "api-secret", Namespace: "default"}, zaptest.NewLogger(t).Sugar())
58-
assert.NoError(t, err)
59-
assert.Equal(t, "1234567890", id)
60-
assert.NotNil(t, c)
61-
})
62-
}
63-
6414
func TestProvider_IsCloudGov(t *testing.T) {
6515
t.Run("should return false for invalid domain", func(t *testing.T) {
66-
p := NewProductionProvider("http://x:namedport", client.ObjectKey{}, nil, false)
16+
p := NewProductionProvider("http://x:namedport", false)
6717
assert.False(t, p.IsCloudGov())
6818
})
6919

7020
t.Run("should return false for commercial Atlas domain", func(t *testing.T) {
71-
p := NewProductionProvider("https://cloud.mongodb.com/", client.ObjectKey{}, nil, false)
21+
p := NewProductionProvider("https://cloud.mongodb.com/", false)
7222
assert.False(t, p.IsCloudGov())
7323
})
7424

7525
t.Run("should return true for Atlas for government domain", func(t *testing.T) {
76-
p := NewProductionProvider("https://cloud.mongodbgov.com/", client.ObjectKey{}, nil, false)
26+
p := NewProductionProvider("https://cloud.mongodbgov.com/", false)
7727
assert.True(t, p.IsCloudGov())
7828
})
7929
}
@@ -173,44 +123,12 @@ func TestProvider_IsResourceSupported(t *testing.T) {
173123

174124
for desc, data := range dataProvider {
175125
t.Run(desc, func(t *testing.T) {
176-
p := NewProductionProvider(data.domain, client.ObjectKey{}, nil, false)
126+
p := NewProductionProvider(data.domain, false)
177127
assert.Equal(t, data.expectation, p.IsResourceSupported(data.resource))
178128
})
179129
}
180130
}
181131

182-
func TestValidateSecretData(t *testing.T) {
183-
t.Run("should be invalid and all missing data", func(t *testing.T) {
184-
missing, ok := validateSecretData(&credentialsSecret{})
185-
assert.False(t, ok)
186-
assert.Equal(t, missing, []string{"orgId", "publicApiKey", "privateApiKey"})
187-
})
188-
189-
t.Run("should be invalid and organization id is missing", func(t *testing.T) {
190-
missing, ok := validateSecretData(&credentialsSecret{PublicKey: "abcdef", PrivateKey: "123456"})
191-
assert.False(t, ok)
192-
assert.Equal(t, missing, []string{"orgId"})
193-
})
194-
195-
t.Run("should be invalid and public key id is missing", func(t *testing.T) {
196-
missing, ok := validateSecretData(&credentialsSecret{OrgID: "abcdef", PrivateKey: "123456"})
197-
assert.False(t, ok)
198-
assert.Equal(t, missing, []string{"publicApiKey"})
199-
})
200-
201-
t.Run("should be invalid and private key id is missing", func(t *testing.T) {
202-
missing, ok := validateSecretData(&credentialsSecret{PublicKey: "abcdef", OrgID: "123456"})
203-
assert.False(t, ok)
204-
assert.Equal(t, missing, []string{"privateApiKey"})
205-
})
206-
207-
t.Run("should be valid", func(t *testing.T) {
208-
missing, ok := validateSecretData(&credentialsSecret{OrgID: "my-org", PublicKey: "abcdef", PrivateKey: "123456"})
209-
assert.True(t, ok)
210-
assert.Empty(t, missing)
211-
})
212-
}
213-
214132
func TestOperatorUserAgent(t *testing.T) {
215133
userAgent := operatorUserAgent()
216134

internal/controller/atlascustomrole/atlascustomrole_controller.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,13 @@ func NewAtlasCustomRoleReconciler(
5050
deletionProtection bool,
5151
independentSyncPeriod time.Duration,
5252
logger *zap.Logger,
53+
globalSecretRef client.ObjectKey,
5354
) *AtlasCustomRoleReconciler {
5455
return &AtlasCustomRoleReconciler{
5556
AtlasReconciler: reconciler.AtlasReconciler{
56-
Client: c.GetClient(),
57-
Log: logger.Named("controllers").Named("AtlasCustomRoles").Sugar(),
57+
Client: c.GetClient(),
58+
Log: logger.Named("controllers").Named("AtlasCustomRoles").Sugar(),
59+
GlobalSecretRef: globalSecretRef,
5860
},
5961
Scheme: c.GetScheme(),
6062
EventRecorder: c.GetEventRecorderFor("AtlasCustomRoles"),
@@ -125,11 +127,11 @@ func (r *AtlasCustomRoleReconciler) Reconcile(ctx context.Context, req ctrl.Requ
125127
fmt.Errorf("the %T is not supported by Atlas for government", atlasCustomRole)), nil
126128
}
127129

128-
credentials, err := r.ResolveCredentials(ctx, atlasCustomRole)
130+
connectionConfig, err := r.ResolveConnectionConfig(ctx, atlasCustomRole)
129131
if err != nil {
130132
return r.fail(req, err), nil
131133
}
132-
atlasSdkClientSet, _, err := r.AtlasProvider.SdkClientSet(workflowCtx.Context, credentials, workflowCtx.Log)
134+
atlasSdkClientSet, err := r.AtlasProvider.SdkClientSet(workflowCtx.Context, connectionConfig.Credentials, workflowCtx.Log)
133135
if err != nil {
134136
return r.terminate(workflowCtx, atlasCustomRole, api.ProjectCustomRolesReadyType, workflow.AtlasAPIAccessNotConfigured, true, err), nil
135137
}

internal/controller/atlascustomrole/atlascustomrole_controller_test.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"go.mongodb.org/atlas-sdk/v20231115008/admin"
1313
"go.mongodb.org/atlas-sdk/v20231115008/mockadmin"
1414
"go.uber.org/zap"
15+
corev1 "k8s.io/api/core/v1"
1516
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1617
"k8s.io/apimachinery/pkg/runtime"
1718
"k8s.io/apimachinery/pkg/types"
@@ -333,9 +334,20 @@ func TestAtlasCustomRoleReconciler_Reconcile(t *testing.T) {
333334
t.Run(name, func(t *testing.T) {
334335
testScheme := runtime.NewScheme()
335336
assert.NoError(t, akov2.AddToScheme(testScheme))
337+
assert.NoError(t, corev1.AddToScheme(testScheme))
336338
k8sClient := fake.NewClientBuilder().
337339
WithScheme(testScheme).
338-
WithObjects(tt.akoCustomRole).
340+
WithObjects(tt.akoCustomRole, &corev1.Secret{
341+
ObjectMeta: metav1.ObjectMeta{
342+
Name: "test",
343+
Namespace: "default",
344+
},
345+
Data: map[string][]byte{
346+
"orgId": []byte("orgId"),
347+
"publicApiKey": []byte("publicApiKey"),
348+
"privateApiKey": []byte("privateApiKey"),
349+
},
350+
}).
339351
WithInterceptorFuncs(tt.interceptors).
340352
Build()
341353
r := &AtlasCustomRoleReconciler{
@@ -346,9 +358,9 @@ func TestAtlasCustomRoleReconciler_Reconcile(t *testing.T) {
346358
Scheme: testScheme,
347359
EventRecorder: record.NewFakeRecorder(10),
348360
AtlasProvider: &atlasmocks.TestProvider{
349-
SdkSetClientFunc: func(secretRef *client.ObjectKey, log *zap.SugaredLogger) (*atlas.ClientSet, string, error) {
361+
SdkClientSetFunc: func(ctx context.Context, creds *atlas.Credentials, log *zap.SugaredLogger) (*atlas.ClientSet, error) {
350362
if tt.sdkShouldError {
351-
return nil, "", fmt.Errorf("failed to create sdk")
363+
return nil, fmt.Errorf("failed to create sdk")
352364
}
353365
cdrAPI := mockadmin.NewCustomDatabaseRolesApi(t)
354366
cdrAPI.EXPECT().GetCustomDatabaseRole(mock.Anything, "testProjectID", "TestRoleName").
@@ -375,7 +387,7 @@ func TestAtlasCustomRoleReconciler_Reconcile(t *testing.T) {
375387
return &atlas.ClientSet{SdkClient20231115008: &admin.APIClient{
376388
CustomDatabaseRolesApi: cdrAPI,
377389
ProjectsApi: pAPI,
378-
}}, "", nil
390+
}}, nil
379391
},
380392
IsCloudGovFunc: func() bool {
381393
return false

0 commit comments

Comments
 (0)