diff --git a/.github/workflows/contract-testing.yaml b/.github/workflows/contract-testing.yaml
index 0ecad68d7..8f1b8c813 100644
--- a/.github/workflows/contract-testing.yaml
+++ b/.github/workflows/contract-testing.yaml
@@ -16,6 +16,7 @@ jobs:
api-key: ${{ steps.filter.outputs.api-key }}
auditing: ${{ steps.filter.outputs.auditing }}
backup-compliance-policy: ${{ steps.filter.outputs.backup-compliance-policy }}
+ cloud-backup-snapshot-export-bucket: ${{ steps.filter.outputs.cloud-backup-snapshot-export-bucket }}
cloud-backup-restore-jobs: ${{ steps.filter.outputs.cloud-backup-restore-jobs }}
cluster-outage-simulation: ${{ steps.filter.outputs.cluster-outage-simulation }}
database-user: ${{ steps.filter.outputs.database-user }}
@@ -66,6 +67,8 @@ jobs:
- 'cfn-resources/auditing/**'
backup-compliance-policy:
- 'cfn-resources/backup-compliance-policy/**'
+ cloud-backup-snapshot-export-bucket:
+ - 'cfn-resources/cloud-backup-snapshot-export-bucket/**'
cloud-backup-restore-jobs:
- 'cfn-resources/cloud-backup-restore-jobs/**'
cluster-outage-simulation:
@@ -335,6 +338,47 @@ jobs:
make run-contract-testing
make delete-test-resources
+ cloud-backup-snapshot-export-bucket:
+ needs: change-detection
+ if: ${{ needs.change-detection.outputs.cloud-backup-snapshot-export-bucket == 'true' }}
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
+ - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5
+ with:
+ go-version-file: 'cfn-resources/go.mod'
+ - name: setup Atlas CLI
+ uses: mongodb/atlas-github-action@e3c9e0204659bafbb3b65e1eb1ee745cca0e9f3b
+ - uses: aws-actions/setup-sam@d78e1a4a9656d3b223e59b80676a797f20093133
+ with:
+ use-installer: true
+ - uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7
+ with:
+ aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_TEST_ENV }}
+ aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_TEST_ENV }}
+ aws-region: eu-west-1
+ - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
+ with:
+ python-version: '3.9'
+ cache: 'pip' # caching pip dependencies
+ - run: pip install cloudformation-cli cloudformation-cli-go-plugin
+ - name: Run the Contract test
+ shell: bash
+ env:
+ MONGODB_ATLAS_PUBLIC_API_KEY: ${{ secrets.CLOUD_DEV_PUBLIC_KEY }}
+ MONGODB_ATLAS_PRIVATE_API_KEY: ${{ secrets.CLOUD_DEV_PRIVATE_KEY }}
+ MONGODB_ATLAS_ORG_ID: ${{ secrets.CLOUD_DEV_ORG_ID }}
+ MONGODB_ATLAS_OPS_MANAGER_URL: ${{ vars.MONGODB_ATLAS_BASE_URL }}
+ MONGODB_ATLAS_PROFILE: cfn-cloud-dev-github-action
+ run: |
+ pushd cfn-resources/cloud-backup-snapshot-export-bucket
+ make create-test-resources
+
+ cat inputs/inputs_1_create.json
+
+ make run-contract-testing
+ make delete-test-resources
+
cloud-backup-restore-jobs:
needs: change-detection
if: ${{ needs.change-detection.outputs.cloud-backup-restore-jobs == 'true' }}
diff --git a/cfn-resources/cloud-backup-snapshot-export-bucket/Makefile b/cfn-resources/cloud-backup-snapshot-export-bucket/Makefile
index f325497fc..4f3d93ebd 100644
--- a/cfn-resources/cloud-backup-snapshot-export-bucket/Makefile
+++ b/cfn-resources/cloud-backup-snapshot-export-bucket/Makefile
@@ -1,4 +1,4 @@
-.PHONY: build debug clean
+.PHONY: build test clean
tags=logging callback metrics scheduler
cgo=0
goos=linux
@@ -13,7 +13,25 @@ build:
debug:
cfn generate
- env GOOS=$(goos) CGO_ENABLED=$(cgo) GOARCH=$(goarch) go build -ldflags="$(ldXflagsD)" -tags="$(tags)" -o bin/bootstrap cmd/main.go
+ env GOOS=$(goos) CGO_ENABLED=$(cgo) GOARCH=$(goarch) go build -ldflags="$(ldXflagsD)" -tags="$(tags)" -o bin/debug cmd/main.go
clean:
rm -rf bin
+
+submit: clean build # submit to private registry must use release build not debug build
+ @echo "==> Submitting to private registry for testing"
+ cfn submit --set-default --region us-east-1
+
+create-test-resources:
+ @echo "==> Creating test files and resources for contract testing"
+ ./test/contract-testing/cfn-test-create.sh
+
+delete-test-resources:
+ @echo "==> Delete test resources used for contract testing"
+ ./test/contract-testing/cfn-test-delete.sh
+
+run-contract-testing:
+ @echo "==> Run contract testing"
+ make build
+ sam local start-lambda &
+ cfn test --function-name TestEntrypoint --verbose
diff --git a/cfn-resources/cloud-backup-snapshot-export-bucket/cmd/resource/config.go b/cfn-resources/cloud-backup-snapshot-export-bucket/cmd/resource/config.go
new file mode 100644
index 000000000..4d9eb7831
--- /dev/null
+++ b/cfn-resources/cloud-backup-snapshot-export-bucket/cmd/resource/config.go
@@ -0,0 +1,19 @@
+// Code generated by 'cfn generate', changes will be undone by the next invocation. DO NOT EDIT.
+// Updates to this type are made my editing the schema file and executing the 'generate' command.
+package resource
+
+import "github.com/aws-cloudformation/cloudformation-cli-go-plugin/cfn/handler"
+
+// TypeConfiguration is autogenerated from the json schema
+type TypeConfiguration struct {
+}
+
+// Configuration returns a resource's configuration.
+func Configuration(req handler.Request) (*TypeConfiguration, error) {
+ // Populate the type configuration
+ typeConfig := &TypeConfiguration{}
+ if err := req.UnmarshalTypeConfig(typeConfig); err != nil {
+ return typeConfig, err
+ }
+ return typeConfig, nil
+}
diff --git a/cfn-resources/cloud-backup-snapshot-export-bucket/cmd/resource/model.go b/cfn-resources/cloud-backup-snapshot-export-bucket/cmd/resource/model.go
index 937e837e2..9458cabf9 100644
--- a/cfn-resources/cloud-backup-snapshot-export-bucket/cmd/resource/model.go
+++ b/cfn-resources/cloud-backup-snapshot-export-bucket/cmd/resource/model.go
@@ -4,9 +4,10 @@ package resource
// Model is autogenerated from the json schema
type Model struct {
- Profile *string `json:",omitempty"`
- BucketName *string `json:",omitempty"`
- ProjectId *string `json:",omitempty"`
- IamRoleID *string `json:",omitempty"`
- Id *string `json:",omitempty"`
+ Profile *string `json:",omitempty"`
+ BucketName *string `json:",omitempty"`
+ ProjectId *string `json:",omitempty"`
+ IamRoleID *string `json:",omitempty"`
+ RequirePrivateNetworking *bool `json:",omitempty"`
+ Id *string `json:",omitempty"`
}
diff --git a/cfn-resources/cloud-backup-snapshot-export-bucket/cmd/resource/resource.go b/cfn-resources/cloud-backup-snapshot-export-bucket/cmd/resource/resource.go
index 5413eb952..6629054ca 100644
--- a/cfn-resources/cloud-backup-snapshot-export-bucket/cmd/resource/resource.go
+++ b/cfn-resources/cloud-backup-snapshot-export-bucket/cmd/resource/resource.go
@@ -19,12 +19,11 @@ import (
"errors"
"github.com/aws-cloudformation/cloudformation-cli-go-plugin/cfn/handler"
- "github.com/aws/aws-sdk-go-v2/aws"
"github.com/mongodb/mongodbatlas-cloudformation-resources/util"
"github.com/mongodb/mongodbatlas-cloudformation-resources/util/constants"
"github.com/mongodb/mongodbatlas-cloudformation-resources/util/progressevent"
"github.com/mongodb/mongodbatlas-cloudformation-resources/util/validator"
- admin20231115002 "go.mongodb.org/atlas-sdk/v20231115002/admin"
+ "go.mongodb.org/atlas-sdk/v20250312013/admin"
)
const (
@@ -53,17 +52,18 @@ func Create(req handler.Request, prevModel *Model, currentModel *Model) (handler
return *pe, nil
}
- params := &admin20231115002.DiskBackupSnapshotAWSExportBucket{
- BucketName: currentModel.BucketName,
- CloudProvider: aws.String(constants.AWS),
- IamRoleId: currentModel.IamRoleID,
+ params := &admin.DiskBackupSnapshotExportBucketRequest{
+ BucketName: currentModel.BucketName,
+ CloudProvider: constants.AWS,
+ IamRoleId: currentModel.IamRoleID,
+ RequirePrivateNetworking: currentModel.RequirePrivateNetworking,
}
- output, resp, err := client.Atlas20231115002.CloudBackupsApi.CreateExportBucket(context.Background(), *currentModel.ProjectId, params).Execute()
+ output, resp, err := client.AtlasSDK.CloudBackupsApi.CreateExportBucket(context.Background(), *currentModel.ProjectId, params).Execute()
if err != nil {
return progressevent.GetFailedEventByResponse(err.Error(), resp), nil
}
- currentModel.Id = output.Id
+ currentModel.Id = &output.Id
return handler.ProgressEvent{
OperationStatus: handler.Success,
@@ -84,7 +84,7 @@ func Read(req handler.Request, prevModel *Model, currentModel *Model) (handler.P
return *pe, nil
}
- output, resp, err := client.Atlas20231115002.CloudBackupsApi.GetExportBucket(context.Background(), *currentModel.ProjectId, *currentModel.Id).Execute()
+ output, resp, err := client.AtlasSDK.CloudBackupsApi.GetExportBucket(context.Background(), *currentModel.ProjectId, *currentModel.Id).Execute()
if err != nil {
return progressevent.GetFailedEventByResponse(err.Error(), resp), nil
}
@@ -114,7 +114,7 @@ func Delete(req handler.Request, prevModel *Model, currentModel *Model) (handler
return *pe, nil
}
- _, resp, err := client.Atlas20231115002.CloudBackupsApi.DeleteExportBucket(context.Background(), *currentModel.ProjectId, *currentModel.Id).Execute()
+ resp, err := client.AtlasSDK.CloudBackupsApi.DeleteExportBucket(context.Background(), *currentModel.ProjectId, *currentModel.Id).Execute()
if err != nil {
return progressevent.GetFailedEventByResponse(err.Error(), resp), nil
}
@@ -137,20 +137,22 @@ func List(req handler.Request, prevModel *Model, currentModel *Model) (handler.P
return *pe, nil
}
- output, resp, err := client.Atlas20231115002.CloudBackupsApi.ListExportBuckets(context.Background(), *currentModel.ProjectId).Execute()
+ output, resp, err := client.AtlasSDK.CloudBackupsApi.ListExportBuckets(context.Background(), *currentModel.ProjectId).Execute()
if err != nil {
return progressevent.GetFailedEventByResponse(err.Error(), resp), nil
}
- resultList := make([]interface{}, 0)
+ resultList := make([]any, 0)
- for i := range output.Results {
- model := Model{
- ProjectId: currentModel.ProjectId,
- Profile: currentModel.Profile,
+ if output.Results != nil {
+ for i := range *output.Results {
+ model := Model{
+ ProjectId: currentModel.ProjectId,
+ Profile: currentModel.Profile,
+ }
+ model.updateModel(&(*output.Results)[i])
+ resultList = append(resultList, model)
}
- model.updateModel(&output.Results[i])
- resultList = append(resultList, model)
}
return handler.ProgressEvent{
@@ -160,8 +162,9 @@ func List(req handler.Request, prevModel *Model, currentModel *Model) (handler.P
}, nil
}
-func (m *Model) updateModel(bucket *admin20231115002.DiskBackupSnapshotAWSExportBucket) {
- m.BucketName = bucket.BucketName
+func (m *Model) updateModel(bucket *admin.DiskBackupSnapshotExportBucketResponse) {
+ m.Id = &bucket.Id
+ m.BucketName = &bucket.BucketName
m.IamRoleID = bucket.IamRoleId
- m.Id = bucket.Id
+ m.RequirePrivateNetworking = bucket.RequirePrivateNetworking
}
diff --git a/cfn-resources/cloud-backup-snapshot-export-bucket/docs/README.md b/cfn-resources/cloud-backup-snapshot-export-bucket/docs/README.md
index ee22d7b3e..afc2e6508 100644
--- a/cfn-resources/cloud-backup-snapshot-export-bucket/docs/README.md
+++ b/cfn-resources/cloud-backup-snapshot-export-bucket/docs/README.md
@@ -16,6 +16,7 @@ To declare this entity in your AWS CloudFormation template, use the following sy
"BucketName" : String,
"ProjectId" : String,
"IamRoleID" : String,
+ "RequirePrivateNetworking" : Boolean,
}
}
@@ -29,6 +30,7 @@ Properties:
BucketName: String
ProjectId: String
IamRoleID: String
+ RequirePrivateNetworking: Boolean
## Properties
@@ -61,9 +63,9 @@ _Required_: Yes
_Type_: String
-_Minimum_: 24
+_Minimum Length_: 24
-_Maximum_: 24
+_Maximum Length_: 24
_Pattern_: ^([a-f0-9]{24})$
@@ -77,14 +79,24 @@ _Required_: Yes
_Type_: String
-_Minimum_: 24
+_Minimum Length_: 24
-_Maximum_: 24
+_Maximum Length_: 24
_Pattern_: ^([a-f0-9]{24})$
_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)
+#### RequirePrivateNetworking
+
+Indicates whether to do exports over PrivateLink as opposed to public IPs. Defaults to false.
+
+_Required_: No
+
+_Type_: Boolean
+
+_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)
+
## Return Values
### Fn::GetAtt
diff --git a/cfn-resources/cloud-backup-snapshot-export-bucket/mongodb-atlas-cloudbackupsnapshotexportbucket.json b/cfn-resources/cloud-backup-snapshot-export-bucket/mongodb-atlas-cloudbackupsnapshotexportbucket.json
index 510b4042b..24bcef53d 100644
--- a/cfn-resources/cloud-backup-snapshot-export-bucket/mongodb-atlas-cloudbackupsnapshotexportbucket.json
+++ b/cfn-resources/cloud-backup-snapshot-export-bucket/mongodb-atlas-cloudbackupsnapshotexportbucket.json
@@ -26,6 +26,10 @@
"minLength": 24,
"pattern": "^([a-f0-9]{24})$"
},
+ "RequirePrivateNetworking": {
+ "type": "boolean",
+ "description": "Indicates whether to do exports over PrivateLink as opposed to public IPs. Defaults to false."
+ },
"Id": {
"type": "string",
"description": "Unique 24-hexadecimal character string that identifies the Amazon Web Services (AWS) Simple Storage Service (S3) export bucket.",
diff --git a/cfn-resources/cloud-backup-snapshot-export-bucket/resource-role.yaml b/cfn-resources/cloud-backup-snapshot-export-bucket/resource-role.yaml
index 206e0482b..8315e6ac0 100644
--- a/cfn-resources/cloud-backup-snapshot-export-bucket/resource-role.yaml
+++ b/cfn-resources/cloud-backup-snapshot-export-bucket/resource-role.yaml
@@ -9,41 +9,28 @@ Resources:
Properties:
MaxSessionDuration: 8400
AssumeRolePolicyDocument:
- Version: '2012-10-17'
+ Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: resources.cloudformation.amazonaws.com
Action: sts:AssumeRole
+ Condition:
+ StringEquals:
+ aws:SourceAccount:
+ Ref: AWS::AccountId
+ StringLike:
+ aws:SourceArn:
+ Fn::Sub: arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:type/resource/MongoDB-Atlas-CloudBackupSnapshotExportBucket/*
Path: "/"
Policies:
- PolicyName: ResourceTypePolicy
PolicyDocument:
- Version: '2012-10-17'
+ Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- - "secretsmanager:CreateSecret"
- - "secretsmanager:DescribeSecret"
- - "secretsmanager:GetSecretValue"
- - "secretsmanager:PutSecretValue"
- - "secretsmanager:UpdateSecretVersionStage"
- - "ec2:CreateVpcEndpoint"
- - "ec2:DeleteVpcEndpoints"
- - "cloudformation:CreateResource"
- - "cloudformation:DeleteResource"
- - "cloudformation:GetResource"
- - "cloudformation:GetResourceRequestStatus"
- - "cloudformation:ListResources"
- - "cloudformation:UpdateResource"
- - "iam:AttachRolePolicy"
- - "iam:CreateRole"
- - "iam:DeleteRole"
- - "iam:GetRole"
- - "iam:GetRolePolicy"
- - "iam:ListAttachedRolePolicies"
- - "iam:ListRolePolicies"
- - "iam:PutRolePolicy"
+ - "secretsmanager:GetSecretValue"
Resource: "*"
Outputs:
ExecutionRoleArn:
diff --git a/cfn-resources/cloud-backup-snapshot-export-bucket/test/cfn-test-create-inputs.sh b/cfn-resources/cloud-backup-snapshot-export-bucket/test/cfn-test-create-inputs.sh
index 8c47fba7e..228fd19d8 100755
--- a/cfn-resources/cloud-backup-snapshot-export-bucket/test/cfn-test-create-inputs.sh
+++ b/cfn-resources/cloud-backup-snapshot-export-bucket/test/cfn-test-create-inputs.sh
@@ -2,93 +2,121 @@
# cfn-test-create-inputs.sh
#
# This tool generates json files in the inputs/ for `cfn test`.
+# It creates all required AWS resources (S3 bucket, IAM role, Cloud Provider Access role)
#
-#set -o errexit
-#set -o nounset
-#set -o pipefail
-function usage {
- echo "Creates a new cloud backup export bucket role for the test"
-}
+set -euo pipefail
+
+rm -rf inputs
+mkdir inputs
+
+profile="default"
+if [ ${MONGODB_ATLAS_PROFILE+x} ]; then
+ echo "profile set to ${MONGODB_ATLAS_PROFILE}"
+ profile=${MONGODB_ATLAS_PROFILE}
+fi
+
+projectName="${1:-$PROJECT_NAME}"
+echo "$projectName"
+
+# Use existing project ID if set, otherwise try to find or create project
+if [ -n "${MONGODB_ATLAS_PROJECT_ID:-}" ]; then
+ projectId="${MONGODB_ATLAS_PROJECT_ID}"
+ echo -e "Using existing project ID from MONGODB_ATLAS_PROJECT_ID: ${projectId}\n"
+else
+ projectId=$(atlas projects list --output json | jq --arg NAME "${projectName}" -r '.results[] | select(.name==$NAME) | .id')
+ if [ -z "$projectId" ]; then
+ projectId=$(atlas projects create "${projectName}" --output=json | jq -r '.id')
+ echo -e "Created project \"${projectName}\" with id: ${projectId}\n"
+ else
+ echo -e "FOUND project \"${projectName}\" with id: ${projectId}\n"
+ fi
+fi
+echo -e "=====\nrun this command to clean up\n=====\natlas projects delete ${projectId} --force\n====="
region=$AWS_DEFAULT_REGION
awsRegion=$AWS_DEFAULT_REGION
if [ -z "$region" ]; then
region=$(aws configure get region)
+ awsRegion=$region
fi
+regionFormatted=$(echo "$region" | sed -e "s/-/_/g" | tr '[:lower:]' '[:upper:]')
+echo "Using region: $region (formatted: $regionFormatted)"
-# shellcheck disable=SC2001
-region=$(echo "$region" | sed -e "s/-/_/g")
-region=$(echo "$region" | tr '[:lower:]' '[:upper:]')
-echo "$region"
+# Use mongodb-atlas-cfn-test-* naming convention (allowed by AWS policy, same as log integration)
+bucketTag="${CFN_TEST_TAG:-$(date +%Y%m%d%H%M%S)}"
+bucketName="mongodb-atlas-cfn-test-export-bucket-${bucketTag}"
+roleName="mongodb-atlas-cloud-backup-export-bucket-$(date +%s)-${RANDOM}"
+policyName="atlas-cloud-backup-export-bucket-S3-role-policy-${regionFormatted}"
-roleName="mongodb-test-cloud-backup-export-bucket-role-${region}"
-policyName="atlas-cloud-backup-export-bucket-S3-role-policy-${region}"
+echo "Bucket name: ${bucketName}"
+echo "Creating IAM role: ${roleName}"
-echo "roleName: ${roleName} , policyName: ${policyName}"
-
-projectName="${1}"
-projectId=$(atlas projects list --output json | jq --arg NAME "${projectName}" -r '.results[] | select(.name==$NAME) | .id')
-if [ -z "$projectId" ]; then
- projectId=$(atlas projects create "${projectName}" --output=json | jq -r '.id')
-
- echo -e "Created project \"${projectName}\" with id: ${projectId}\n"
-else
- echo -e "FOUND project \"${projectName}\" with id: ${projectId}\n"
-fi
-
-#------------ CREATING AtlAS ROLE -------------------
+# Create cloud provider access entry
roleID=$(atlas cloudProviders accessRoles aws create --projectId "${projectId}" --output json | jq -r '.roleId')
-echo -e "--------------------------------Mongo CLI Role creation ends ----------------------------\n"
+echo "Created Atlas cloud provider access entry: ${roleID}"
+
+# Get Atlas AWS Account ARN and External ID
+atlasAWSAccountArn=$(atlas cloudProviders accessRoles list --projectId "${projectId}" --output json | jq --arg roleID "${roleID}" -r '.awsIamRoles[] | select(.roleId | test($roleID)) | .atlasAWSAccountArn')
+atlasAssumedRoleExternalId=$(atlas cloudProviders accessRoles list --projectId "${projectId}" --output json | jq --arg roleID "${roleID}" -r '.awsIamRoles[] | select(.roleId | test($roleID)) | .atlasAssumedRoleExternalId')
-#------------ Get role information-------------------
-atlasAWSAccountArn=$(atlas cloudProviders accessRoles list --projectId "${projectId}" --output json | jq --arg roleID "${roleID}" -r '.awsIamRoles[] |select(.roleId |test( $roleID)) |.atlasAWSAccountArn')
-atlasAssumedRoleExternalId=$(atlas cloudProviders accessRoles --projectId "${projectId}" list --output json | jq --arg roleID "${roleID}" -r '.awsIamRoles[] |select(.roleId |test( $roleID)) |.atlasAssumedRoleExternalId')
+# Create trust policy
jq --arg atlasAssumedRoleExternalId "$atlasAssumedRoleExternalId" \
--arg atlasAWSAccountArn "$atlasAWSAccountArn" \
- '.Statement[0].Principal.AWS?|=$atlasAWSAccountArn | .Statement[0].Condition.StringEquals["sts:ExternalId"]?|=$atlasAssumedRoleExternalId' "$(dirname "$0")/role-policy-template.json" >"$(dirname "$0")/add-policy.json"
+ '.Statement[0].Principal.AWS?|=$atlasAWSAccountArn | .Statement[0].Condition.StringEquals["sts:ExternalId"]?|=$atlasAssumedRoleExternalId' \
+ "$(dirname "$0")/role-policy-template.json" >"$(dirname "$0")/add-policy.json"
-#------------ Create aws Iam role-------------------
+echo "--------------------------------AWS Role creation starts----------------------------"
-awsRoleID=$(aws iam get-role --role-name "${roleName}" | jq --arg roleName "${roleName}" -r '.Role | select(.RoleName==$roleName) |.RoleId')
-if [ -z "$awsRoleID" ]; then
- awsRoleID=$(aws iam create-role --role-name "${roleName}" --assume-role-policy-document "file://$(dirname "$0")/add-policy.json" | jq --arg roleName "${roleName}" -r '.Role | select(.RoleName==$roleName) |.RoleId')
- aws iam put-role-policy --role-name "${roleName}" --policy-name "${policyName}" --policy-document "file://$(dirname "$0")/policy.json"
- echo -e "No role found, hence creating the role: ${awsRoleID}\n"
-
- sleep 30 # Role Arn not returning immediately
+# Check if role exists, delete if found (must remove inline policy first)
+awsRoleId=$(aws iam get-role --role-name "${roleName}" 2>/dev/null | jq -r '.Role.RoleId' || echo "")
+if [ -n "$awsRoleId" ]; then
+ aws iam delete-role-policy --role-name "${roleName}" --policy-name "${policyName}" 2>/dev/null || true
+ aws iam delete-role --role-name "${roleName}"
+ echo "Deleted existing role"
fi
-echo -e "--------------------------------AWS Role creation ends ----------------------------\n"
-#------------ get Role arn-------------------
-awsArn=$(aws iam get-role --role-name "${roleName}" | jq --arg roleName "${roleName}" -r '.Role | select(.RoleName==$roleName) |.Arn')
+# Create IAM role
+awsRoleId=$(aws iam create-role --role-name "${roleName}" --assume-role-policy-document file://"$(dirname "$0")/add-policy.json" | jq -r '.Role.RoleId')
+echo "Created AWS IAM role: ${awsRoleId}"
-echo -e "--------------------------------attach mongodb Role to AWS Role ends ----------------------------\n"
+# Get role ARN
+awsRoleArn=$(aws iam get-role --role-name "${roleName}" | jq -r '.Role.Arn')
-# shellcheck disable=SC2001
-awsArne=$(echo "${awsArn}" | sed 's/"//g')
-# shellcheck disable=SC2086
-# TODO Needs change to while loop using get operation
-sleep 30
+echo "--------------------------------AWS Role creation ends----------------------------"
-atlas cloudProviders accessRoles aws authorize "${roleID}" --projectId "${projectId}" --iamAssumedRoleArn "${awsArne}"
-echo -e "--------------------------------authorize mongodb Role ends ----------------------------\n"
+# Wait for AWS IAM role to propagate (similar to encryption-at-rest / stream-connection pattern)
+echo "Waiting for IAM role to propagate..."
+sleep 65
-#create the s3 bucket
+# Authorize the role in Atlas
+echo "--------------------------------Authorize MongoDB Atlas Role starts----------------------------"
+atlas cloudProviders accessRoles aws authorize "${roleID}" --projectId "${projectId}" --iamAssumedRoleArn "${awsRoleArn}"
+echo "Authorized role: ${roleName}"
+echo "--------------------------------Authorize MongoDB Atlas Role ends----------------------------"
-bucketName="cloud-backup-snapshot-${CFN_TEST_TAG}-${awsRegion}"
+echo "--------------------------------Creating S3 Bucket----------------------------"
+if aws s3 ls "s3://${bucketName}" 2>/dev/null; then
+ aws s3 rb "s3://${bucketName}" --force
+fi
+aws s3 mb "s3://${bucketName}" --region "${awsRegion}"
+echo "Created S3 bucket: ${bucketName}"
-aws s3 rb "s3://${bucketName}" --force
-aws s3 mb "s3://${bucketName}" --output json
+echo "--------------------------------Attaching S3 policy to IAM role----------------------------"
+aws iam put-role-policy \
+ --role-name "${roleName}" \
+ --policy-name "${policyName}" \
+ --policy-document "file://$(dirname "$0")/policy.json"
+echo "--------------------------------attach mongodb Role to AWS Role ends----------------------------"
-if [ "$#" -ne 2 ]; then usage; fi
-if [[ "$*" == help ]]; then usage; fi
-
-rm -rf inputs
-mkdir inputs
+# Save role name for cleanup (delete script reads this when run from resource root)
+echo "${roleName}" > "$(dirname "$0")/role-name.txt"
jq --arg projectId "$projectId" \
- --arg iamRoleID "$roleID" \
--arg bucketName "$bucketName" \
- '.ProjectId?|=$projectId | .IamRoleID?|=$iamRoleID | .BucketName?|=$bucketName ' \
+ --arg iamRoleID "$roleID" \
+ --arg profile "$profile" \
+ '.Profile?|=$profile | .ProjectId?|=$projectId | .BucketName?|=$bucketName | .IamRoleID?|=$iamRoleID' \
"$(dirname "$0")/inputs_1_create.template.json" >"inputs/inputs_1_create.json"
+
+ls -l inputs
diff --git a/cfn-resources/cloud-backup-snapshot-export-bucket/test/cfn-test-delete-inputs.sh b/cfn-resources/cloud-backup-snapshot-export-bucket/test/cfn-test-delete-inputs.sh
index c082f894e..b96fe465c 100755
--- a/cfn-resources/cloud-backup-snapshot-export-bucket/test/cfn-test-delete-inputs.sh
+++ b/cfn-resources/cloud-backup-snapshot-export-bucket/test/cfn-test-delete-inputs.sh
@@ -1,48 +1,60 @@
#!/usr/bin/env bash
-# cfn-test-create-inputs.sh
+# cfn-test-delete-inputs.sh
#
-# This tool generates json files in the inputs/ for `cfn test`.
+# This tool deletes the mongodb and AWS resources used for `cfn test` as inputs.
+# Run from resource root (e.g. make delete-test-resources).
#
-echo "--------------------------------delete key and key policy document policy document starts ----------------------------"
-projectName="${1}"
-projectId=$(atlas projects list --output json | jq --arg NAME "${projectName}" -r '.results[] | select(.name==$NAME) | .id')
-echo "Check if a project is created $projectId"
-export MCLI_PROJECT_ID=$projectId
+set -euo pipefail
+
+projectId=$(jq -r '.ProjectId' ./inputs/inputs_1_create.json)
+bucketName=$(jq -r '.BucketName' ./inputs/inputs_1_create.json)
+roleId=$(jq -r '.IamRoleID' ./inputs/inputs_1_create.json)
+
+echo "Deleting resources for projectId: ${projectId}"
+
+# Get IAM role name from file saved by create script
+scriptDir="$(dirname "$0")"
+if [ -f "${scriptDir}/role-name.txt" ]; then
+ roleName=$(cat "${scriptDir}/role-name.txt")
+ echo "Using role name from role-name.txt: ${roleName}"
+else
+ echo "role-name.txt not found, skipping IAM role cleanup"
+ roleName=""
+fi
keyRegion=$AWS_DEFAULT_REGION
-awsRegion=$AWS_DEFAULT_REGION
if [ -z "$keyRegion" ]; then
keyRegion=$(aws configure get region)
fi
# shellcheck disable=SC2001
-keyRegion=$(echo "$keyRegion" | sed -e "s/-/_/g")
-keyRegion=$(echo "$keyRegion" | tr '[:lower:]' '[:upper:]')
-echo "$keyRegion"
-
-roleName="mongodb-test-cloud-backup-export-bucket-role-${keyRegion}"
+keyRegion=$(echo "$keyRegion" | sed -e "s/-/_/g" | tr '[:lower:]' '[:upper:]')
policyName="atlas-cloud-backup-export-bucket-S3-role-policy-${keyRegion}"
-pwd
-trustPolicy=$(jq '.Statement[0].Condition.StringEquals["sts:ExternalId"]' "add-policy.json")
-echo "$trustPolicy"
-roleExternalID=$(${trustPolicy##*/})
-# shellcheck disable=SC2001
-atlasAssumedRoleExternalID=$(echo "${roleExternalID}" | sed 's/"//g')
-echo "$atlasAssumedRoleExternalID"
-
-roleId=$(atlas cloudProviders accessRoles list --output json --projectId "${projectId}" | jq --arg roleID "${atlasAssumedRoleExternalID}" -r '.awsIamRoles[] |select(.atlasAssumedRoleExternalId |test( $roleID)) |.roleId')
-echo "$roleId"
-
-atlas cloudProviders accessRoles aws deauthorize "${roleId}" --projectId "${projectId}" --force
-echo "--------------------------------delete role starts ----------------------------"
+# Deauthorize Atlas role
+if [ -n "$roleId" ] && [ "$roleId" != "null" ]; then
+ echo "Deauthorizing Atlas role: ${roleId}"
+ atlas cloudProviders accessRoles aws deauthorize "${roleId}" --projectId "${projectId}" --force || echo "Failed to deauthorize role"
+fi
-aws iam delete-role-policy --role-name "$roleName" --policy-name "$policyName"
-aws iam delete-role --role-name "$roleName"
-echo "--------------------------------delete role ends ----------------------------"
+# Delete AWS IAM role
+if [ -n "$roleName" ] && [ "$roleName" != "" ]; then
+ echo "--------------------------------delete AWS IAM role starts----------------------------"
+ aws iam delete-role-policy --role-name "$roleName" --policy-name "$policyName" 2>/dev/null || true
+ aws iam delete-role --role-name "$roleName" 2>/dev/null || true
+ echo "--------------------------------delete AWS IAM role ends----------------------------"
+fi
-bucketName="cloud-backup-snapshot-${CFN_TEST_TAG}-${awsRegion}"
+# Delete S3 bucket
+if [ -n "$bucketName" ] && [ "$bucketName" != "null" ]; then
+ echo "Deleting S3 bucket: ${bucketName}"
+ aws s3 rb "s3://${bucketName}" --force 2>/dev/null || true
+fi
-aws s3 rb s3://"${bucketName}" --force
-echo "--------------------------------delete bucket ends ----------------------------"
-#mongocli iam projects delete "${projectId}" --force
+# Delete project
+if atlas projects delete "$projectId" --force; then
+ echo "${projectId} project deletion OK"
+else
+ echo "Failed cleaning project: ${projectId}"
+ exit 1
+fi
diff --git a/cfn-resources/cloud-backup-snapshot-export-bucket/test/contract-testing/cfn-test-create.sh b/cfn-resources/cloud-backup-snapshot-export-bucket/test/contract-testing/cfn-test-create.sh
new file mode 100755
index 000000000..5161b2ed7
--- /dev/null
+++ b/cfn-resources/cloud-backup-snapshot-export-bucket/test/contract-testing/cfn-test-create.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+
+# This tool generates the resources and json files in the inputs/ for `cfn test`.
+set -o errexit
+set -o nounset
+set -o pipefail
+
+if [ -z "${AWS_DEFAULT_REGION+x}" ]; then
+ echo "AWS_DEFAULT_REGION must be set"
+ exit 1
+fi
+
+projectName="cfn-export-bucket-$(date +%s)-$RANDOM"
+
+echo "projectName: $projectName"
+
+./test/cfn-test-create-inputs.sh "$projectName"
diff --git a/cfn-resources/cloud-backup-snapshot-export-bucket/test/contract-testing/cfn-test-delete.sh b/cfn-resources/cloud-backup-snapshot-export-bucket/test/contract-testing/cfn-test-delete.sh
new file mode 100755
index 000000000..071210dd9
--- /dev/null
+++ b/cfn-resources/cloud-backup-snapshot-export-bucket/test/contract-testing/cfn-test-delete.sh
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+
+# This tool deletes the mongodb and AWS resources used for `cfn test` as inputs.
+set -o errexit
+set -o nounset
+set -o pipefail
+
+# Run from resource root so ./inputs/ and ./test/ exist
+scriptDir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+resourceRoot="$(dirname "$(dirname "$scriptDir")")"
+cd "$resourceRoot"
+
+./test/cfn-test-delete-inputs.sh
diff --git a/cfn-resources/cloud-backup-snapshot-export-bucket/test/inputs_1_create.template.json b/cfn-resources/cloud-backup-snapshot-export-bucket/test/inputs_1_create.template.json
index 1240fe76a..50a6a9c23 100644
--- a/cfn-resources/cloud-backup-snapshot-export-bucket/test/inputs_1_create.template.json
+++ b/cfn-resources/cloud-backup-snapshot-export-bucket/test/inputs_1_create.template.json
@@ -2,5 +2,6 @@
"ProjectId": "",
"IamRoleID": "",
"BucketName": "",
- "Profile": "default"
+ "Profile": "default",
+ "RequirePrivateNetworking": "false"
}
diff --git a/examples/cloud-backup-snapshot-export-bucket/CloudBackupSnapshotExportBucket.json b/examples/cloud-backup-snapshot-export-bucket/CloudBackupSnapshotExportBucket.json
index 5bd212ff4..266301b9f 100644
--- a/examples/cloud-backup-snapshot-export-bucket/CloudBackupSnapshotExportBucket.json
+++ b/examples/cloud-backup-snapshot-export-bucket/CloudBackupSnapshotExportBucket.json
@@ -36,7 +36,8 @@
},
"Profile": {
"Ref": "Profile"
- }
+ },
+ "RequirePrivateNetworking": false
}
}
},