Skip to content

Commit effc369

Browse files
Merge branch 'master' into CLOUDP-369809-private-endpoint-aws
2 parents b76ebe0 + 2941cea commit effc369

95 files changed

Lines changed: 5345 additions & 529 deletions

File tree

Some content is hidden

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

.github/workflows/contract-testing.yaml

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ jobs:
3030
search-deployment: ${{ steps.filter.outputs.search-deployment }}
3131
stream-connection: ${{ steps.filter.outputs.stream-connection }}
3232
stream-instance: ${{ steps.filter.outputs.stream-instance }}
33+
stream-processor: ${{ steps.filter.outputs.stream-processor }}
3334
stream-workspace: ${{ steps.filter.outputs.stream-workspace }}
3435
steps:
3536
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
@@ -76,6 +77,8 @@ jobs:
7677
- 'cfn-resources/stream-connection/**'
7778
stream-instance:
7879
- 'cfn-resources/stream-instance/**'
80+
stream-processor:
81+
- 'cfn-resources/stream-processor/**'
7982
stream-workspace:
8083
- 'cfn-resources/stream-workspace/**'
8184
access-list-api-key:
@@ -855,6 +858,46 @@ jobs:
855858
pushd cfn-resources/stream-instance
856859
make create-test-resources
857860
cat inputs/inputs_1_create.json
861+
make run-contract-testing
862+
make delete-test-resources
863+
stream-processor:
864+
needs: change-detection
865+
if: ${{ needs.change-detection.outputs.stream-processor == 'true' }}
866+
runs-on: ubuntu-latest
867+
steps:
868+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
869+
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5
870+
with:
871+
go-version-file: 'cfn-resources/go.mod'
872+
- name: setup Atlas CLI
873+
uses: mongodb/atlas-github-action@e3c9e0204659bafbb3b65e1eb1ee745cca0e9f3b
874+
- uses: aws-actions/setup-sam@c2a20b1822cc4a6bc594ff7f1dbb658758e383c3
875+
with:
876+
use-installer: true
877+
- uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708
878+
with:
879+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_TEST_ENV }}
880+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_TEST_ENV }}
881+
aws-region: eu-west-1
882+
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548
883+
with:
884+
python-version: '3.9'
885+
cache: 'pip' # caching pip dependencies
886+
- run: pip install cloudformation-cli cloudformation-cli-go-plugin
887+
- name: Run the Contract test
888+
shell: bash
889+
env:
890+
MONGODB_ATLAS_PUBLIC_API_KEY: ${{ secrets.CLOUD_DEV_PUBLIC_KEY }}
891+
MONGODB_ATLAS_PRIVATE_API_KEY: ${{ secrets.CLOUD_DEV_PRIVATE_KEY }}
892+
MONGODB_ATLAS_ORG_ID: ${{ secrets.CLOUD_DEV_ORG_ID }}
893+
MONGODB_ATLAS_OPS_MANAGER_URL: ${{ vars.MONGODB_ATLAS_BASE_URL }}
894+
MONGODB_ATLAS_PROFILE: cfn-cloud-dev-github-action
895+
run: |
896+
cd cfn-resources/stream-processor
897+
make create-test-resources
898+
899+
cat inputs/*
900+
858901
make run-contract-testing
859902
make delete-test-resources
860903
stream-workspace:
@@ -897,4 +940,4 @@ jobs:
897940
cat inputs/inputs_1_update.json
898941
899942
make run-contract-testing
900-
make delete-test-resources
943+
make delete-test-resources

cfn-resources/organization/cmd/resource/config.go

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cfn-resources/organization/cmd/resource/model.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cfn-resources/organization/cmd/resource/resource.go

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -217,10 +217,37 @@ func Delete(req handler.Request, prevModel *Model, currentModel *Model) (handler
217217

218218
// If exists
219219
_, response, err = currentModel.getOrgDetails(ctx, conn, currentModel)
220-
if err != nil && response.StatusCode == http.StatusUnauthorized {
220+
if err != nil && util.StatusUnauthorized(response) {
221221
return handleError(response, constants.DELETE, err)
222222
}
223223

224+
currentModel.IsDeleted = util.Pointer(false)
225+
226+
responseMsg, progressEvent := runDelete(ctx, conn, currentModel)
227+
if responseMsg.Error != nil {
228+
// Retry once on transient server error, waiting 20 seconds before retrying request
229+
// This covers case of contract tests which create and delete an org within seconds, encountering an error while deleting the org
230+
if responseMsg.Response != nil && responseMsg.Response.StatusCode == http.StatusInternalServerError {
231+
_, _ = logger.Warnf("Transient server error while deleting organization, retrying in 20 seconds")
232+
time.Sleep(20 * time.Second)
233+
responseMsg, progressEvent = runDelete(ctx, conn, currentModel)
234+
}
235+
}
236+
if progressEvent != nil {
237+
return *progressEvent, nil
238+
}
239+
if responseMsg.Error != nil {
240+
return handleError(responseMsg.Response, constants.DELETE, responseMsg.Error)
241+
}
242+
243+
return handler.ProgressEvent{
244+
OperationStatus: handler.Success,
245+
Message: DeleteCompleted,
246+
ResourceModel: nil}, nil
247+
}
248+
249+
// Encapsulate the delete+wait logic so the same flow can be used on retry.
250+
func runDelete(ctx context.Context, conn *admin.APIClient, currentModel *Model) (*DeleteResponse, *handler.ProgressEvent) {
224251
deleteRequest := conn.OrganizationsApi.DeleteOrg(ctx, *currentModel.OrgId)
225252

226253
// Since the Delete API is synchronous and takes more than 1 minute most of the time,
@@ -234,38 +261,29 @@ func Delete(req handler.Request, prevModel *Model, currentModel *Model) (handler
234261
responseChan <- DeleteResponse{Error: err, Response: response}
235262
}()
236263

237-
currentModel.IsDeleted = util.Pointer(false)
238264
select {
239265
case responseMsg := <-responseChan:
240-
if responseMsg.Error != nil {
241-
return handleError(responseMsg.Response, constants.DELETE, responseMsg.Error)
242-
}
243-
266+
return &responseMsg, nil
244267
case <-time.After(30 * time.Second):
245268
// If the Delete is not completed in the above time,
246269
// we return a progress event with inProgress status and callback context
247-
return handler.ProgressEvent{
270+
return nil, &handler.ProgressEvent{
248271
OperationStatus: handler.InProgress,
249272
Message: DeleteInProgress,
250273
ResourceModel: currentModel,
251274
CallbackDelaySeconds: CallBackSeconds,
252275
CallbackContext: map[string]interface{}{
253276
constants.StateName: DeletingState,
254277
},
255-
}, nil
278+
}
256279
}
257-
258-
return handler.ProgressEvent{
259-
OperationStatus: handler.Success,
260-
Message: DeleteCompleted,
261-
ResourceModel: nil}, nil
262280
}
263281

264282
func deleteCallback(ctx context.Context, conn *admin.APIClient, currentModel *Model) (handler.ProgressEvent, error) {
265283
// Read before delete
266284
org, response, err := currentModel.getOrgDetails(ctx, conn, currentModel)
267285
if err != nil {
268-
if response.StatusCode == http.StatusUnauthorized {
286+
if util.StatusUnauthorized(response) {
269287
return handler.ProgressEvent{
270288
OperationStatus: handler.Success,
271289
Message: DeleteCompleted,
@@ -315,28 +333,29 @@ func (model *Model) getOrgDetails(ctx context.Context, conn *admin.APIClient, cu
315333
model.MultiFactorAuthRequired = settings.MultiFactorAuthRequired
316334
model.RestrictEmployeeAccess = settings.RestrictEmployeeAccess
317335
model.GenAIFeaturesEnabled = settings.GenAIFeaturesEnabled
336+
model.SecurityContact = settings.SecurityContact
318337

319338
return model, response, nil
320339
}
321340

322341
func handleError(response *http.Response, method constants.CfnFunctions, err error) (handler.ProgressEvent, error) {
323342
errMsg := fmt.Sprintf("%s error:%s", method, err.Error())
324343
_, _ = logger.Warn(errMsg)
325-
if response.StatusCode == http.StatusConflict {
344+
if util.StatusConflict(response) {
326345
return handler.ProgressEvent{
327346
OperationStatus: handler.Failed,
328347
Message: errMsg,
329348
HandlerErrorCode: string(types.HandlerErrorCodeAlreadyExists)}, nil
330349
}
331350

332-
if response.StatusCode == http.StatusUnauthorized {
351+
if util.StatusUnauthorized(response) {
333352
return handler.ProgressEvent{
334353
OperationStatus: handler.Failed,
335354
Message: "Not found",
336355
HandlerErrorCode: string(types.HandlerErrorCodeNotFound)}, nil
337356
}
338357

339-
if response.StatusCode == http.StatusBadRequest {
358+
if util.StatusBadRequest(response) {
340359
return handler.ProgressEvent{
341360
OperationStatus: handler.Failed,
342361
Message: errMsg,
@@ -359,6 +378,7 @@ func newOrganizationSettings(model *Model) *admin.OrganizationSettings {
359378
MultiFactorAuthRequired: model.MultiFactorAuthRequired,
360379
RestrictEmployeeAccess: model.RestrictEmployeeAccess,
361380
GenAIFeaturesEnabled: model.GenAIFeaturesEnabled,
381+
SecurityContact: model.SecurityContact,
362382
}
363383
}
364384

cfn-resources/organization/docs/README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ To declare this entity in your AWS CloudFormation template, use the following sy
2323
"<a href="#isdeleted" title="IsDeleted">IsDeleted</a>" : <i>Boolean</i>,
2424
"<a href="#apiaccesslistrequired" title="ApiAccessListRequired">ApiAccessListRequired</a>" : <i>Boolean</i>,
2525
"<a href="#multifactorauthrequired" title="MultiFactorAuthRequired">MultiFactorAuthRequired</a>" : <i>Boolean</i>,
26-
"<a href="#restrictemployeeaccess" title="RestrictEmployeeAccess">RestrictEmployeeAccess</a>" : <i>Boolean</i>
26+
"<a href="#restrictemployeeaccess" title="RestrictEmployeeAccess">RestrictEmployeeAccess</a>" : <i>Boolean</i>,
27+
"<a href="#securitycontact" title="SecurityContact">SecurityContact</a>" : <i>String</i>
2728
}
2829
}
2930
</pre>
@@ -45,6 +46,7 @@ Properties:
4546
<a href="#apiaccesslistrequired" title="ApiAccessListRequired">ApiAccessListRequired</a>: <i>Boolean</i>
4647
<a href="#multifactorauthrequired" title="MultiFactorAuthRequired">MultiFactorAuthRequired</a>: <i>Boolean</i>
4748
<a href="#restrictemployeeaccess" title="RestrictEmployeeAccess">RestrictEmployeeAccess</a>: <i>Boolean</i>
49+
<a href="#securitycontact" title="SecurityContact">SecurityContact</a>: <i>String</i>
4850
</pre>
4951

5052
## Properties
@@ -173,6 +175,16 @@ _Type_: Boolean
173175

174176
_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)
175177

178+
#### SecurityContact
179+
180+
Email address of the security contact for the organization.
181+
182+
_Required_: No
183+
184+
_Type_: String
185+
186+
_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)
187+
176188
## Return Values
177189

178190
### Fn::GetAtt

cfn-resources/organization/mongodb-atlas-organization.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@
8383
"RestrictEmployeeAccess": {
8484
"type": "boolean",
8585
"description": "Flag that indicates whether to block MongoDB Support from accessing Atlas infrastructure 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/."
86+
},
87+
"SecurityContact": {
88+
"type": "string",
89+
"description": "Email address of the security contact for the organization."
8690
}
8791
},
8892
"additionalProperties": false,

cfn-resources/organization/test/inputs_1_create.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@
1414
"RestrictEmployeeAccess": "false",
1515
"ApiAccessListRequired": "false",
1616
"SkipDefaultAlertsSettings": "true",
17-
"GenAIFeaturesEnabled": "true"
17+
"GenAIFeaturesEnabled": "true",
18+
"SecurityContact": "security-test@example.com"
1819
}

cfn-resources/organization/test/inputs_1_update.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@
1414
"RestrictEmployeeAccess": "true",
1515
"ApiAccessListRequired": "false",
1616
"SkipDefaultAlertsSettings": "false",
17-
"GenAIFeaturesEnabled": "false"
17+
"GenAIFeaturesEnabled": "false",
18+
"SecurityContact": "security-updated@example.com"
1819
}

cfn-resources/search-deployment/cmd/resource/mappings.go

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414

1515
package resource
1616

17-
import admin20231115014 "go.mongodb.org/atlas-sdk/v20231115014/admin"
17+
import "go.mongodb.org/atlas-sdk/v20250312012/admin"
1818

19-
func NewCFNSearchDeployment(prevModel *Model, apiResp *admin20231115014.ApiSearchDeploymentResponse) Model {
19+
func NewCFNSearchDeployment(prevModel *Model, apiResp *admin.ApiSearchDeploymentResponse) Model {
2020
respSpecs := apiResp.GetSpecs()
2121
resultSpecs := make([]ApiSearchDeploymentSpec, len(respSpecs))
2222
for i := range respSpecs {
@@ -25,25 +25,27 @@ func NewCFNSearchDeployment(prevModel *Model, apiResp *admin20231115014.ApiSearc
2525
NodeCount: &respSpecs[i].NodeCount,
2626
}
2727
}
28+
2829
return Model{
29-
Profile: prevModel.Profile,
30-
ClusterName: prevModel.ClusterName,
31-
ProjectId: prevModel.ProjectId,
32-
Id: apiResp.Id,
33-
Specs: resultSpecs,
34-
StateName: apiResp.StateName,
30+
Profile: prevModel.Profile,
31+
ClusterName: prevModel.ClusterName,
32+
ProjectId: prevModel.ProjectId,
33+
Id: apiResp.Id,
34+
Specs: resultSpecs,
35+
StateName: apiResp.StateName,
36+
EncryptionAtRestProvider: apiResp.EncryptionAtRestProvider,
3537
}
3638
}
3739

38-
func NewSearchDeploymentReq(model *Model) admin20231115014.ApiSearchDeploymentRequest {
40+
func NewSearchDeploymentReq(model *Model) admin.ApiSearchDeploymentRequest {
3941
modelSpecs := model.Specs
40-
requestSpecs := make([]admin20231115014.ApiSearchDeploymentSpec, len(modelSpecs))
42+
requestSpecs := make([]admin.ApiSearchDeploymentSpec, len(modelSpecs))
4143
for i, spec := range modelSpecs {
4244
// Both spec fields are required in CFN model and will be defined
43-
requestSpecs[i] = admin20231115014.ApiSearchDeploymentSpec{
45+
requestSpecs[i] = admin.ApiSearchDeploymentSpec{
4446
InstanceSize: *spec.InstanceSize,
4547
NodeCount: *spec.NodeCount,
4648
}
4749
}
48-
return admin20231115014.ApiSearchDeploymentRequest{Specs: requestSpecs}
50+
return admin.ApiSearchDeploymentRequest{Specs: requestSpecs}
4951
}

0 commit comments

Comments
 (0)