diff --git a/.github/workflows/contract-testing.yaml b/.github/workflows/contract-testing.yaml index 21c4f122d..ab660c710 100644 --- a/.github/workflows/contract-testing.yaml +++ b/.github/workflows/contract-testing.yaml @@ -15,6 +15,7 @@ jobs: alert-configuration: ${{ steps.filter.outputs.alert-configuration }} api-key: ${{ steps.filter.outputs.api-key }} auditing: ${{ steps.filter.outputs.auditing }} + backup-compliance-policy: ${{ steps.filter.outputs.backup-compliance-policy }} cloud-backup-restore-jobs: ${{ steps.filter.outputs.cloud-backup-restore-jobs }} cluster-outage-simulation: ${{ steps.filter.outputs.cluster-outage-simulation }} federated-database-instance: ${{ steps.filter.outputs.federated-database-instance }} @@ -48,6 +49,8 @@ jobs: - 'cfn-resources/api-key/**' auditing: - 'cfn-resources/auditing/**' + backup-compliance-policy: + - 'cfn-resources/backup-compliance-policy/**' cloud-backup-restore-jobs: - 'cfn-resources/cloud-backup-restore-jobs/**' cluster-outage-simulation: @@ -243,7 +246,48 @@ jobs: cat inputs/inputs_1_update.json cat inputs/inputs_2_create.json cat inputs/inputs_2_update.json - + + make run-contract-testing + make delete-test-resources + backup-compliance-policy: + needs: change-detection + if: ${{ needs.change-detection.outputs.backup-compliance-policy == 'true' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + - 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@c2a20b1822cc4a6bc594ff7f1dbb658758e383c3 + with: + use-installer: true + - uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 + 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@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 + 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: | + cd cfn-resources/backup-compliance-policy + make create-test-resources + + cat inputs/inputs_1_create.json + cat inputs/inputs_1_update.json + make run-contract-testing make delete-test-resources cloud-backup-restore-jobs: diff --git a/cfn-resources/backup-compliance-policy/.rpdk-config b/cfn-resources/backup-compliance-policy/.rpdk-config new file mode 100644 index 000000000..9b9328eff --- /dev/null +++ b/cfn-resources/backup-compliance-policy/.rpdk-config @@ -0,0 +1,12 @@ +{ + "typeName": "MongoDB::Atlas::BackupCompliancePolicy", + "language": "go", + "runtime": "provided.al2", + "entrypoint": "bootstrap", + "testEntrypoint": "bootstrap", + "settings": { + "import_path": "github.com/mongodb/mongodbatlas-cloudformation-resources/backup-compliance-policy", + "protocolVersion": "2.0.0", + "pluginVersion": "2.0.4" + } +} diff --git a/cfn-resources/backup-compliance-policy/Makefile b/cfn-resources/backup-compliance-policy/Makefile new file mode 100644 index 000000000..f0e934f54 --- /dev/null +++ b/cfn-resources/backup-compliance-policy/Makefile @@ -0,0 +1,33 @@ +.PHONY: build test clean +tags=logging callback metrics scheduler +cgo=0 +goos=linux +goarch=amd64 +CFNREP_GIT_SHA?=$(shell git rev-parse HEAD) +ldXflags=-s -w -X github.com/mongodb/mongodbatlas-cloudformation-resources/util.defaultLogLevel=info -X github.com/mongodb/mongodbatlas-cloudformation-resources/version.Version=${CFNREP_GIT_SHA} +ldXflagsD=-X github.com/mongodb/mongodbatlas-cloudformation-resources/util.defaultLogLevel=debug -X github.com/mongodb/mongodbatlas-cloudformation-resources/version.Version=${CFNREP_GIT_SHA} + +build: + cfn generate + env GOOS=$(goos) CGO_ENABLED=$(cgo) GOARCH=$(goarch) go build -ldflags="$(ldXflags)" -tags="$(tags)" -o bin/bootstrap cmd/main.go + +debug: + cfn generate + env GOOS=$(goos) CGO_ENABLED=$(cgo) GOARCH=$(goarch) go build -ldflags="$(ldXflagsD)" -tags="$(tags)" -o bin/bootstrap cmd/main.go + +clean: + rm -rf bin + +create-test-resources: + @echo "==> Creating test files 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/backup-compliance-policy/README.md b/cfn-resources/backup-compliance-policy/README.md new file mode 100644 index 000000000..4cafba23d --- /dev/null +++ b/cfn-resources/backup-compliance-policy/README.md @@ -0,0 +1,18 @@ +# MongoDB::Atlas::BackupCompliancePolicy + +## Description + +Resource for managing [Backup Compliance Policy](https://www.mongodb.com/docs/api/doc/atlas-admin-api-v2/group/endpoint-cloud-backups/operation/updateCompliancePolicy). Backup Compliance Policy prevents any user, regardless of role, from modifying or deleting specific cluster settings, backups, and backup configurations. When enabled, the Backup Compliance Policy will be applied as the minimum policy for all clusters and backups in the project. + +## Requirements + +To securely give CloudFormation access to your Atlas credentials, you must +set up an [AWS Profile](/README.md#mongodb-atlas-api-keys-credential-management). + +## Attributes and Parameters + +See the [resource docs](docs/README.md). + +## Cloudformation Examples + +See the examples [CFN Template](/examples/backup-compliance-policy/README.md) for example resource. diff --git a/cfn-resources/backup-compliance-policy/cmd/main.go b/cfn-resources/backup-compliance-policy/cmd/main.go new file mode 100644 index 000000000..7736f1876 --- /dev/null +++ b/cfn-resources/backup-compliance-policy/cmd/main.go @@ -0,0 +1,85 @@ +// Code generated by 'cfn generate', changes will be undone by the next invocation. DO NOT EDIT. +package main + +import ( + "errors" + "fmt" + "log" + + "github.com/aws-cloudformation/cloudformation-cli-go-plugin/cfn" + "github.com/aws-cloudformation/cloudformation-cli-go-plugin/cfn/handler" + "github.com/mongodb/mongodbatlas-cloudformation-resources/backup-compliance-policy/cmd/resource" +) + +// Handler is a container for the CRUDL actions exported by resources +type Handler struct{} + +// Create wraps the related Create function exposed by the resource code +func (r *Handler) Create(req handler.Request) handler.ProgressEvent { + return wrap(req, resource.Create) +} + +// Read wraps the related Read function exposed by the resource code +func (r *Handler) Read(req handler.Request) handler.ProgressEvent { + return wrap(req, resource.Read) +} + +// Update wraps the related Update function exposed by the resource code +func (r *Handler) Update(req handler.Request) handler.ProgressEvent { + return wrap(req, resource.Update) +} + +// Delete wraps the related Delete function exposed by the resource code +func (r *Handler) Delete(req handler.Request) handler.ProgressEvent { + return wrap(req, resource.Delete) +} + +// List wraps the related List function exposed by the resource code +func (r *Handler) List(req handler.Request) handler.ProgressEvent { + return wrap(req, resource.List) +} + +// main is the entry point of the application. +func main() { + cfn.Start(&Handler{}) +} + +type handlerFunc func(handler.Request, *resource.Model, *resource.Model) (handler.ProgressEvent, error) + +func wrap(req handler.Request, f handlerFunc) (response handler.ProgressEvent) { + defer func() { + // Catch any panics and return a failed ProgressEvent + if r := recover(); r != nil { + err, ok := r.(error) + if !ok { + err = errors.New(fmt.Sprint(r)) + } + + log.Printf("Trapped error in handler: %v", err) + + response = handler.NewFailedEvent(err) + } + }() + + // Populate the previous model + prevModel := &resource.Model{} + if err := req.UnmarshalPrevious(prevModel); err != nil { + log.Printf("Error unmarshaling prev model: %v", err) + return handler.NewFailedEvent(err) + } + + // Populate the current model + currentModel := &resource.Model{} + if err := req.Unmarshal(currentModel); err != nil { + log.Printf("Error unmarshaling model: %v", err) + return handler.NewFailedEvent(err) + } + + response, err := f(req, prevModel, currentModel) + if err != nil { + log.Printf("Error returned from handler function: %v", err) + return handler.NewFailedEvent(err) + } + + return response +} diff --git a/cfn-resources/backup-compliance-policy/cmd/resource/config.go b/cfn-resources/backup-compliance-policy/cmd/resource/config.go new file mode 100644 index 000000000..4d9eb7831 --- /dev/null +++ b/cfn-resources/backup-compliance-policy/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/backup-compliance-policy/cmd/resource/handlers.go b/cfn-resources/backup-compliance-policy/cmd/resource/handlers.go new file mode 100644 index 000000000..0c8fee9d4 --- /dev/null +++ b/cfn-resources/backup-compliance-policy/cmd/resource/handlers.go @@ -0,0 +1,326 @@ +// Copyright 2026 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package resource + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/aws-cloudformation/cloudformation-cli-go-plugin/cfn/handler" + "github.com/aws/aws-sdk-go-v2/service/cloudformation/types" + "go.mongodb.org/atlas-sdk/v20250312012/admin" + + "github.com/mongodb/mongodbatlas-cloudformation-resources/util" + "github.com/mongodb/mongodbatlas-cloudformation-resources/util/constants" + "github.com/mongodb/mongodbatlas-cloudformation-resources/util/logger" + progress_events "github.com/mongodb/mongodbatlas-cloudformation-resources/util/progressevent" +) + +const ( + policyStateActive = "ACTIVE" + errorCannotUpdateWithPendingAction = "CANNOT_UPDATE_BACKUP_COMPLIANCE_POLICY_SETTINGS_WITH_PENDING_ACTION" + errorCannotDisableBackupCompliancePolicy = "CANNOT_DISABLE_BACKUP_COMPLIANCE_POLICY" +) + +var callbackContext = map[string]any{"callbackBackupCompliancePolicy": true} + +func IsCallback(req *handler.Request) bool { + _, found := req.CallbackContext["callbackBackupCompliancePolicy"] + return found +} + +func isPendingActionError(err error) bool { + if err == nil { + return false + } + return strings.Contains(err.Error(), errorCannotUpdateWithPendingAction) +} + +func handlePendingAction(ctx context.Context, client *util.MongoDBClient, model *Model, projectID string) handler.ProgressEvent { + policy, _, getErr := client.AtlasSDK.CloudBackupsApi.GetCompliancePolicy(ctx, projectID).Execute() + if getErr == nil && policy != nil { + SetBackupCompliancePolicyData(model, policy) + } + return inProgressEvent(model, policy) +} + +func checkPolicyNotFound(policy *admin.DataProtectionSettings20231001, apiResp *http.Response, err error, projectID string, operation constants.CfnFunctions) *handler.ProgressEvent { + if err != nil { + if util.StatusNotFound(apiResp) { + pe := progress_events.GetFailedEventByCode( + "Backup Compliance Policy not found for project: "+projectID, + string(types.HandlerErrorCodeNotFound)) + return &pe + } + pe := handleError(apiResp, operation, err) + return &pe + } + + if policy != nil && policy.GetProjectId() == "" { + pe := progress_events.GetFailedEventByCode( + "Backup Compliance Policy not found for project: "+projectID, + string(types.HandlerErrorCodeNotFound)) + return &pe + } + + return nil +} + +func HandleCreate(req *handler.Request, client *util.MongoDBClient, model *Model) handler.ProgressEvent { + if IsCallback(req) { + return validateProgress(client, model, false, constants.CREATE) + } + + ctx := context.Background() + projectID := *model.ProjectId + + existingPolicy, apiResp, err := client.AtlasSDK.CloudBackupsApi.GetCompliancePolicy(ctx, projectID).Execute() + if err != nil { + if !util.StatusNotFound(apiResp) { + return handleError(apiResp, constants.CREATE, err) + } + } else if existingPolicy != nil && existingPolicy.GetState() == policyStateActive { + return handler.ProgressEvent{ + OperationStatus: handler.Failed, + Message: "Backup Compliance Policy already exists for project: " + projectID, + HandlerErrorCode: string(types.HandlerErrorCodeAlreadyExists), + } + } + + dataProtectionSettings := ExpandDataProtectionSettings(model, projectID) + + params := admin.UpdateCompliancePolicyApiParams{ + GroupId: projectID, + DataProtectionSettings20231001: dataProtectionSettings, + OverwriteBackupPolicies: util.Pointer(false), + } + + _, apiResp, err = client.AtlasSDK.CloudBackupsApi.UpdateCompliancePolicyWithParams(ctx, ¶ms).Execute() + if err != nil { + if isPendingActionError(err) { + return handlePendingAction(ctx, client, model, projectID) + } + return handleError(apiResp, constants.CREATE, err) + } + + policy, apiResp, err := client.AtlasSDK.CloudBackupsApi.GetCompliancePolicy(ctx, projectID).Execute() + if err != nil { + return handleError(apiResp, constants.CREATE, err) + } + + return inProgressEvent(model, policy) +} + +func HandleRead(req *handler.Request, client *util.MongoDBClient, model *Model) handler.ProgressEvent { + ctx := context.Background() + projectID := *model.ProjectId + + policy, apiResp, err := client.AtlasSDK.CloudBackupsApi.GetCompliancePolicy(ctx, projectID).Execute() + if pe := checkPolicyNotFound(policy, apiResp, err, projectID, constants.READ); pe != nil { + return *pe + } + + SetBackupCompliancePolicyData(model, policy) + + return handler.ProgressEvent{ + OperationStatus: handler.Success, + Message: constants.ReadComplete, + ResourceModel: model, + } +} + +func HandleUpdate(req *handler.Request, client *util.MongoDBClient, model *Model) handler.ProgressEvent { + if IsCallback(req) { + return validateProgress(client, model, false, constants.UPDATE) + } + + ctx := context.Background() + projectID := *model.ProjectId + + policy, apiResp, err := client.AtlasSDK.CloudBackupsApi.GetCompliancePolicy(ctx, projectID).Execute() + if pe := checkPolicyNotFound(policy, apiResp, err, projectID, constants.UPDATE); pe != nil { + return *pe + } + + dataProtectionSettings := ExpandDataProtectionSettings(model, projectID) + + params := admin.UpdateCompliancePolicyApiParams{ + GroupId: projectID, + DataProtectionSettings20231001: dataProtectionSettings, + OverwriteBackupPolicies: util.Pointer(false), + } + + _, apiResp, err = client.AtlasSDK.CloudBackupsApi.UpdateCompliancePolicyWithParams(ctx, ¶ms).Execute() + if err != nil { + if isPendingActionError(err) { + return handlePendingAction(ctx, client, model, projectID) + } + return handleError(apiResp, constants.UPDATE, err) + } + + policy, apiResp, err = client.AtlasSDK.CloudBackupsApi.GetCompliancePolicy(ctx, projectID).Execute() + if err != nil { + return handleError(apiResp, constants.UPDATE, err) + } + + return inProgressEvent(model, policy) +} + +func HandleDelete(req *handler.Request, client *util.MongoDBClient, model *Model) handler.ProgressEvent { + if IsCallback(req) { + return validateProgress(client, model, true, constants.DELETE) + } + + ctx := context.Background() + projectID := *model.ProjectId + + policy, apiResp, err := client.AtlasSDK.CloudBackupsApi.GetCompliancePolicy(ctx, projectID).Execute() + if pe := checkPolicyNotFound(policy, apiResp, err, projectID, constants.DELETE); pe != nil { + return *pe + } + + apiResp, err = client.AtlasSDK.CloudBackupsApi.DisableCompliancePolicy(ctx, projectID).Execute() + if err == nil { + return inProgressEvent(model, nil) + } + + errorMessage := err.Error() + + if strings.Contains(errorMessage, errorCannotDisableBackupCompliancePolicy) { + _, _ = logger.Warnf("Cannot disable backup compliance policy for project %s: Policy requires MongoDB support or removing all clusters & retained snapshots", projectID) + return handler.ProgressEvent{ + OperationStatus: handler.Failed, + Message: fmt.Sprintf("Cannot disable Backup Compliance Policy for project %s. Disabling BCP requires MongoDB support or removing all clusters & retained snapshots. This can only be done by filing a support ticket.", projectID), + HandlerErrorCode: string(types.HandlerErrorCodeInvalidRequest), + } + } + + if isPendingActionError(err) { + return handlePendingAction(ctx, client, model, projectID) + } + + if util.StatusNotFound(apiResp) { + return handler.ProgressEvent{ + OperationStatus: handler.Success, + Message: constants.Complete, + } + } + + _, _ = logger.Warnf("Unexpected delete error for project %s: %s", projectID, errorMessage) + return handleError(apiResp, constants.DELETE, err) +} + +func HandleList(req *handler.Request, client *util.MongoDBClient, model *Model) handler.ProgressEvent { + ctx := context.Background() + projectID := *model.ProjectId + + policy, apiResp, err := client.AtlasSDK.CloudBackupsApi.GetCompliancePolicy(ctx, projectID).Execute() + if err != nil { + if !util.StatusNotFound(apiResp) { + return handleError(apiResp, constants.LIST, err) + } + return handler.ProgressEvent{ + OperationStatus: handler.Success, + Message: constants.Complete, + ResourceModels: []any{}, + } + } + + if policy != nil && policy.GetProjectId() == "" { + return handler.ProgressEvent{ + OperationStatus: handler.Success, + Message: constants.Complete, + ResourceModels: []any{}, + } + } + + SetBackupCompliancePolicyData(model, policy) + + return handler.ProgressEvent{ + OperationStatus: handler.Success, + Message: constants.Complete, + ResourceModels: []any{model}, + } +} + +func inProgressEvent(model *Model, policy *admin.DataProtectionSettings20231001) handler.ProgressEvent { + if policy != nil { + SetBackupCompliancePolicyData(model, policy) + } + return handler.ProgressEvent{ + OperationStatus: handler.InProgress, + Message: constants.Pending, + ResourceModel: model, + CallbackDelaySeconds: callBackSeconds, + CallbackContext: callbackContext, + } +} + +func validateProgress(client *util.MongoDBClient, model *Model, isDelete bool, operation constants.CfnFunctions) handler.ProgressEvent { + ctx := context.Background() + projectID := *model.ProjectId + + policy, resp, err := client.AtlasSDK.CloudBackupsApi.GetCompliancePolicy(ctx, projectID).Execute() + notFound := util.StatusNotFound(resp) + policyDeleted := notFound || (policy != nil && policy.GetProjectId() == "") + + if err != nil && !notFound { + if isPendingActionError(err) { + return inProgressEvent(model, policy) + } + return handleError(resp, operation, err) + } + + if isDelete && policyDeleted { + return handler.ProgressEvent{ + OperationStatus: handler.Success, + Message: constants.Complete, + } + } + + if isDelete { + // Don't call DisableCompliancePolicy again - it always returns 204 + return inProgressEvent(model, nil) + } + + if policy != nil { + SetBackupCompliancePolicyData(model, policy) + if policy.GetState() == policyStateActive { + return handler.ProgressEvent{ + OperationStatus: handler.Success, + Message: constants.Complete, + ResourceModel: model, + } + } + return inProgressEvent(model, policy) + } + + if notFound { + return handler.ProgressEvent{ + OperationStatus: handler.Failed, + Message: "Backup Compliance Policy was deleted during operation", + HandlerErrorCode: string(types.HandlerErrorCodeNotFound), + } + } + + return inProgressEvent(model, policy) +} + +func handleError(response *http.Response, method constants.CfnFunctions, err error) handler.ProgressEvent { + errMsg := fmt.Sprintf("%s error:%s", method, err.Error()) + return progress_events.GetFailedEventByResponse(errMsg, response) +} diff --git a/cfn-resources/backup-compliance-policy/cmd/resource/mappings.go b/cfn-resources/backup-compliance-policy/cmd/resource/mappings.go new file mode 100644 index 000000000..cb0047ae1 --- /dev/null +++ b/cfn-resources/backup-compliance-policy/cmd/resource/mappings.go @@ -0,0 +1,248 @@ +// Copyright 2026 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package resource + +import ( + "go.mongodb.org/atlas-sdk/v20250312012/admin" + + "github.com/mongodb/mongodbatlas-cloudformation-resources/util" +) + +const ( + Hourly = "hourly" + Daily = "daily" + Weekly = "weekly" + Monthly = "monthly" + Yearly = "yearly" +) + +func isOnDemandPolicyItemEmpty(item *OnDemandPolicyItem) bool { + return item == nil || (item.FrequencyInterval == nil && item.RetentionUnit == nil && item.RetentionValue == nil) +} + +func isScheduledPolicyItemEmpty(item *ScheduledPolicyItem) bool { + return item == nil || (item.FrequencyInterval == nil && item.RetentionUnit == nil && item.RetentionValue == nil) +} + +func SetBackupCompliancePolicyData(currentModel *Model, policy *admin.DataProtectionSettings20231001) { + if policy == nil { + return + } + + projectID := policy.GetProjectId() + currentModel.ProjectId = &projectID + + authorizedEmail := policy.GetAuthorizedEmail() + currentModel.AuthorizedEmail = &authorizedEmail + + authorizedUserFirstName := policy.GetAuthorizedUserFirstName() + currentModel.AuthorizedUserFirstName = &authorizedUserFirstName + + authorizedUserLastName := policy.GetAuthorizedUserLastName() + currentModel.AuthorizedUserLastName = &authorizedUserLastName + + currentModel.CopyProtectionEnabled = policy.CopyProtectionEnabled + currentModel.EncryptionAtRestEnabled = policy.EncryptionAtRestEnabled + currentModel.PitEnabled = policy.PitEnabled + currentModel.RestoreWindowDays = policy.RestoreWindowDays + + if policy.OnDemandPolicyItem != nil { + currentModel.OnDemandPolicyItem = GetOnDemandPolicyItem(policy.OnDemandPolicyItem) + } + + if policy.ScheduledPolicyItems != nil { + currentModel.PolicyItemHourly = nil + currentModel.PolicyItemDaily = nil + currentModel.PolicyItemWeekly = []ScheduledPolicyItem{} + currentModel.PolicyItemMonthly = []ScheduledPolicyItem{} + currentModel.PolicyItemYearly = []ScheduledPolicyItem{} + + for _, item := range *policy.ScheduledPolicyItems { + frequencyType := item.GetFrequencyType() + scheduledItem := GetScheduledPolicyItem(&item) + switch frequencyType { + case Hourly: + currentModel.PolicyItemHourly = scheduledItem + case Daily: + currentModel.PolicyItemDaily = scheduledItem + case Weekly: + currentModel.PolicyItemWeekly = append(currentModel.PolicyItemWeekly, *scheduledItem) + case Monthly: + currentModel.PolicyItemMonthly = append(currentModel.PolicyItemMonthly, *scheduledItem) + case Yearly: + currentModel.PolicyItemYearly = append(currentModel.PolicyItemYearly, *scheduledItem) + } + } + } + + state := policy.GetState() + currentModel.State = &state + if policy.UpdatedDate != nil { + updatedDateStr := util.TimeToString(*policy.UpdatedDate) + currentModel.UpdatedDate = &updatedDateStr + } + updatedUser := policy.GetUpdatedUser() + currentModel.UpdatedUser = &updatedUser +} + +func GetOnDemandPolicyItem(item *admin.BackupComplianceOnDemandPolicyItem) *OnDemandPolicyItem { + if item == nil { + return nil + } + frequencyType := item.GetFrequencyType() + retentionUnit := item.GetRetentionUnit() + frequencyInterval := item.GetFrequencyInterval() + retentionValue := item.GetRetentionValue() + return &OnDemandPolicyItem{ + Id: item.Id, + FrequencyInterval: &frequencyInterval, + FrequencyType: &frequencyType, + RetentionUnit: &retentionUnit, + RetentionValue: &retentionValue, + } +} + +func GetScheduledPolicyItem(item *admin.BackupComplianceScheduledPolicyItem) *ScheduledPolicyItem { + if item == nil { + return nil + } + frequencyType := item.GetFrequencyType() + retentionUnit := item.GetRetentionUnit() + frequencyInterval := item.GetFrequencyInterval() + retentionValue := item.GetRetentionValue() + return &ScheduledPolicyItem{ + Id: item.Id, + FrequencyType: &frequencyType, + FrequencyInterval: &frequencyInterval, + RetentionUnit: &retentionUnit, + RetentionValue: &retentionValue, + } +} + +func ExpandDataProtectionSettings(model *Model, projectID string) *admin.DataProtectionSettings20231001 { + authorizedEmail := "" + if model.AuthorizedEmail != nil { + authorizedEmail = *model.AuthorizedEmail + } + authorizedUserFirstName := "" + if model.AuthorizedUserFirstName != nil { + authorizedUserFirstName = *model.AuthorizedUserFirstName + } + authorizedUserLastName := "" + if model.AuthorizedUserLastName != nil { + authorizedUserLastName = *model.AuthorizedUserLastName + } + + copyProtectionEnabled := false + if model.CopyProtectionEnabled != nil { + copyProtectionEnabled = *model.CopyProtectionEnabled + } + encryptionAtRestEnabled := false + if model.EncryptionAtRestEnabled != nil { + encryptionAtRestEnabled = *model.EncryptionAtRestEnabled + } + pitEnabled := false + if model.PitEnabled != nil { + pitEnabled = *model.PitEnabled + } + restoreWindowDays := 0 + if model.RestoreWindowDays != nil { + restoreWindowDays = *model.RestoreWindowDays + } + + settings := &admin.DataProtectionSettings20231001{ + ProjectId: &projectID, + AuthorizedEmail: authorizedEmail, + AuthorizedUserFirstName: authorizedUserFirstName, + AuthorizedUserLastName: authorizedUserLastName, + CopyProtectionEnabled: ©ProtectionEnabled, + EncryptionAtRestEnabled: &encryptionAtRestEnabled, + PitEnabled: &pitEnabled, + RestoreWindowDays: &restoreWindowDays, + } + + if !isOnDemandPolicyItemEmpty(model.OnDemandPolicyItem) { + settings.OnDemandPolicyItem = ExpandOnDemandPolicyItem(model.OnDemandPolicyItem) + } + + var scheduledItems []admin.BackupComplianceScheduledPolicyItem + + if !isScheduledPolicyItemEmpty(model.PolicyItemHourly) { + scheduledItems = append(scheduledItems, ExpandScheduledPolicyItem(model.PolicyItemHourly, Hourly)) + } + if !isScheduledPolicyItemEmpty(model.PolicyItemDaily) { + scheduledItems = append(scheduledItems, ExpandScheduledPolicyItem(model.PolicyItemDaily, Daily)) + } + if len(model.PolicyItemWeekly) > 0 { + for _, item := range model.PolicyItemWeekly { + scheduledItems = append(scheduledItems, ExpandScheduledPolicyItem(&item, Weekly)) + } + } + if len(model.PolicyItemMonthly) > 0 { + for _, item := range model.PolicyItemMonthly { + scheduledItems = append(scheduledItems, ExpandScheduledPolicyItem(&item, Monthly)) + } + } + if len(model.PolicyItemYearly) > 0 { + for _, item := range model.PolicyItemYearly { + scheduledItems = append(scheduledItems, ExpandScheduledPolicyItem(&item, Yearly)) + } + } + + if len(scheduledItems) > 0 { + settings.ScheduledPolicyItems = &scheduledItems + } + + return settings +} + +func ExpandOnDemandPolicyItem(item *OnDemandPolicyItem) *admin.BackupComplianceOnDemandPolicyItem { + if item == nil { + return nil + } + onDemandPolicy := &admin.BackupComplianceOnDemandPolicyItem{ + Id: item.Id, + FrequencyType: "ondemand", + } + if item.FrequencyInterval != nil { + onDemandPolicy.FrequencyInterval = *item.FrequencyInterval + } + if item.RetentionValue != nil { + onDemandPolicy.RetentionValue = *item.RetentionValue + } + if item.RetentionUnit != nil { + onDemandPolicy.RetentionUnit = *item.RetentionUnit + } + return onDemandPolicy +} + +func ExpandScheduledPolicyItem(item *ScheduledPolicyItem, frequencyType string) admin.BackupComplianceScheduledPolicyItem { + scheduledPolicy := admin.BackupComplianceScheduledPolicyItem{ + FrequencyType: frequencyType, + } + if item == nil { + return scheduledPolicy + } + if item.FrequencyInterval != nil { + scheduledPolicy.FrequencyInterval = *item.FrequencyInterval + } + if item.RetentionValue != nil { + scheduledPolicy.RetentionValue = *item.RetentionValue + } + if item.RetentionUnit != nil { + scheduledPolicy.RetentionUnit = *item.RetentionUnit + } + return scheduledPolicy +} diff --git a/cfn-resources/backup-compliance-policy/cmd/resource/mappings_test.go b/cfn-resources/backup-compliance-policy/cmd/resource/mappings_test.go new file mode 100644 index 000000000..f83a54a6b --- /dev/null +++ b/cfn-resources/backup-compliance-policy/cmd/resource/mappings_test.go @@ -0,0 +1,261 @@ +// Copyright 2026 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package resource_test + +import ( + "testing" + "time" + + "github.com/mongodb/mongodbatlas-cloudformation-resources/backup-compliance-policy/cmd/resource" + "github.com/mongodb/mongodbatlas-cloudformation-resources/util" + "github.com/stretchr/testify/assert" + "go.mongodb.org/atlas-sdk/v20250312012/admin" +) + +func TestPolicyItemConversions(t *testing.T) { + t.Run("getOnDemandPolicyItem", func(t *testing.T) { + assert.Nil(t, resource.GetOnDemandPolicyItem(nil)) + + item := &admin.BackupComplianceOnDemandPolicyItem{ + Id: util.StringPtr("id-1"), + FrequencyType: "ondemand", + FrequencyInterval: 1, + RetentionUnit: "days", + RetentionValue: 7, + } + result := resource.GetOnDemandPolicyItem(item) + assert.Equal(t, "id-1", *result.Id) + assert.Equal(t, 1, *result.FrequencyInterval) + assert.Equal(t, 7, *result.RetentionValue) + }) + + t.Run("expandOnDemandPolicyItem", func(t *testing.T) { + assert.Nil(t, resource.ExpandOnDemandPolicyItem(nil)) + + freqInterval := 2 + retentionValue := 4 + model := &resource.OnDemandPolicyItem{ + FrequencyInterval: &freqInterval, + RetentionUnit: util.StringPtr("weeks"), + RetentionValue: &retentionValue, + } + result := resource.ExpandOnDemandPolicyItem(model) + assert.Equal(t, "ondemand", result.FrequencyType) + assert.Equal(t, 2, result.FrequencyInterval) + assert.Equal(t, 4, result.RetentionValue) + }) + + t.Run("getScheduledPolicyItem", func(t *testing.T) { + assert.Nil(t, resource.GetScheduledPolicyItem(nil)) + + item := &admin.BackupComplianceScheduledPolicyItem{ + FrequencyType: "hourly", + FrequencyInterval: 6, + RetentionUnit: "days", + RetentionValue: 3, + } + result := resource.GetScheduledPolicyItem(item) + assert.Equal(t, "hourly", *result.FrequencyType) + assert.Equal(t, 6, *result.FrequencyInterval) + }) + + t.Run("expandScheduledPolicyItem", func(t *testing.T) { + freqInterval := 1 + retentionValue := 4 + model := &resource.ScheduledPolicyItem{ + FrequencyInterval: &freqInterval, + RetentionUnit: util.StringPtr("weeks"), + RetentionValue: &retentionValue, + } + result := resource.ExpandScheduledPolicyItem(model, "daily") + assert.Equal(t, "daily", result.FrequencyType) + assert.Equal(t, 1, result.FrequencyInterval) + }) +} + +func TestSetBackupCompliancePolicyData(t *testing.T) { + tests := map[string]struct { + policy *admin.DataProtectionSettings20231001 + check func(*testing.T, *resource.Model) + }{ + "nil policy": { + policy: nil, + check: func(t *testing.T, m *resource.Model) { + t.Helper() + assert.Nil(t, m.ProjectId) + }, + }, + "basic fields": { + policy: &admin.DataProtectionSettings20231001{ + ProjectId: util.StringPtr("proj-123"), + AuthorizedEmail: "admin@example.com", + CopyProtectionEnabled: util.Pointer(true), + RestoreWindowDays: util.IntPtr(7), + State: util.StringPtr("ACTIVE"), + }, + check: func(t *testing.T, m *resource.Model) { + t.Helper() + assert.Equal(t, "proj-123", *m.ProjectId) + assert.Equal(t, "admin@example.com", *m.AuthorizedEmail) + assert.True(t, *m.CopyProtectionEnabled) + assert.Equal(t, 7, *m.RestoreWindowDays) + assert.Equal(t, "ACTIVE", *m.State) + }, + }, + "with scheduled items": { + policy: &admin.DataProtectionSettings20231001{ + ProjectId: util.StringPtr("proj-456"), + AuthorizedEmail: "test@example.com", + ScheduledPolicyItems: &[]admin.BackupComplianceScheduledPolicyItem{ + {FrequencyType: "hourly", FrequencyInterval: 6, RetentionUnit: "days", RetentionValue: 3}, + {FrequencyType: "daily", FrequencyInterval: 1, RetentionUnit: "weeks", RetentionValue: 1}, + {FrequencyType: "weekly", FrequencyInterval: 1, RetentionUnit: "months", RetentionValue: 1}, + }, + State: util.StringPtr("ACTIVE"), + }, + check: func(t *testing.T, m *resource.Model) { + t.Helper() + assert.NotNil(t, m.PolicyItemHourly) + assert.NotNil(t, m.PolicyItemDaily) + assert.Len(t, m.PolicyItemWeekly, 1) + assert.Equal(t, "hourly", *m.PolicyItemHourly.FrequencyType) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + model := &resource.Model{} + resource.SetBackupCompliancePolicyData(model, tc.policy) + tc.check(t, model) + }) + } +} + +func TestExpandDataProtectionSettings(t *testing.T) { + tests := map[string]struct { + model *resource.Model + check func(*testing.T, *admin.DataProtectionSettings20231001) + }{ + "with empty policy items": { + model: &resource.Model{ + AuthorizedEmail: util.StringPtr("admin@example.com"), + OnDemandPolicyItem: &resource.OnDemandPolicyItem{}, + PolicyItemHourly: &resource.ScheduledPolicyItem{}, + PolicyItemDaily: &resource.ScheduledPolicyItem{}, + }, + check: func(t *testing.T, s *admin.DataProtectionSettings20231001) { + t.Helper() + assert.Nil(t, s.OnDemandPolicyItem, "empty OnDemandPolicyItem should be omitted") + assert.Nil(t, s.ScheduledPolicyItems, "empty policy items should not create scheduled items") + }, + }, + "minimal model": { + model: &resource.Model{ + AuthorizedEmail: util.StringPtr("admin@example.com"), + AuthorizedUserFirstName: util.StringPtr("Jane"), + AuthorizedUserLastName: util.StringPtr("Smith"), + }, + check: func(t *testing.T, s *admin.DataProtectionSettings20231001) { + t.Helper() + assert.Equal(t, "admin@example.com", s.AuthorizedEmail) + assert.Equal(t, "Jane", s.AuthorizedUserFirstName) + assert.False(t, *s.CopyProtectionEnabled) + }, + }, + "with booleans and integers": { + model: &resource.Model{ + AuthorizedEmail: util.StringPtr("test@example.com"), + CopyProtectionEnabled: util.Pointer(true), + EncryptionAtRestEnabled: util.Pointer(true), + PitEnabled: util.Pointer(false), + RestoreWindowDays: util.IntPtr(14), + }, + check: func(t *testing.T, s *admin.DataProtectionSettings20231001) { + t.Helper() + assert.True(t, *s.CopyProtectionEnabled) + assert.True(t, *s.EncryptionAtRestEnabled) + assert.False(t, *s.PitEnabled) + assert.Equal(t, 14, *s.RestoreWindowDays) + }, + }, + "with all policy items": { + model: &resource.Model{ + AuthorizedEmail: util.StringPtr("admin@example.com"), + PolicyItemHourly: &resource.ScheduledPolicyItem{ + FrequencyInterval: util.IntPtr(6), + RetentionUnit: util.StringPtr("days"), + RetentionValue: util.IntPtr(3), + }, + PolicyItemDaily: &resource.ScheduledPolicyItem{ + FrequencyInterval: util.IntPtr(1), + RetentionUnit: util.StringPtr("weeks"), + RetentionValue: util.IntPtr(1), + }, + PolicyItemWeekly: []resource.ScheduledPolicyItem{ + { + FrequencyInterval: util.IntPtr(1), + RetentionUnit: util.StringPtr("months"), + RetentionValue: util.IntPtr(2), + }, + }, + }, + check: func(t *testing.T, s *admin.DataProtectionSettings20231001) { + t.Helper() + assert.NotNil(t, s.ScheduledPolicyItems) + items := *s.ScheduledPolicyItems + assert.Len(t, items, 3) + assert.Equal(t, "hourly", items[0].FrequencyType) + assert.Equal(t, "daily", items[1].FrequencyType) + assert.Equal(t, "weekly", items[2].FrequencyType) + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + result := resource.ExpandDataProtectionSettings(tc.model, "test-project") + assert.Equal(t, "test-project", *result.ProjectId) + tc.check(t, result) + }) + } +} + +func TestRoundTripConversion(t *testing.T) { + t.Run("policy survives round trip", func(t *testing.T) { + original := &admin.DataProtectionSettings20231001{ + ProjectId: util.StringPtr("proj-999"), + AuthorizedEmail: "security@example.com", + AuthorizedUserFirstName: "Test", + AuthorizedUserLastName: "User", + CopyProtectionEnabled: util.Pointer(true), + PitEnabled: util.Pointer(false), + RestoreWindowDays: util.IntPtr(7), + State: util.StringPtr("ACTIVE"), + UpdatedDate: &time.Time{}, + UpdatedUser: util.StringPtr("admin"), + } + + model := &resource.Model{} + resource.SetBackupCompliancePolicyData(model, original) + + result := resource.ExpandDataProtectionSettings(model, "proj-999") + + assert.Equal(t, *original.ProjectId, *result.ProjectId) + assert.Equal(t, original.AuthorizedEmail, result.AuthorizedEmail) + assert.Equal(t, *original.CopyProtectionEnabled, *result.CopyProtectionEnabled) + assert.Equal(t, *original.RestoreWindowDays, *result.RestoreWindowDays) + }) +} diff --git a/cfn-resources/backup-compliance-policy/cmd/resource/model.go b/cfn-resources/backup-compliance-policy/cmd/resource/model.go new file mode 100644 index 000000000..dd180c1d9 --- /dev/null +++ b/cfn-resources/backup-compliance-policy/cmd/resource/model.go @@ -0,0 +1,43 @@ +// 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 + +// Model is autogenerated from the json schema +type Model struct { + Profile *string `json:",omitempty"` + ProjectId *string `json:",omitempty"` + AuthorizedEmail *string `json:",omitempty"` + AuthorizedUserFirstName *string `json:",omitempty"` + AuthorizedUserLastName *string `json:",omitempty"` + CopyProtectionEnabled *bool `json:",omitempty"` + EncryptionAtRestEnabled *bool `json:",omitempty"` + RestoreWindowDays *int `json:",omitempty"` + OnDemandPolicyItem *OnDemandPolicyItem `json:",omitempty"` + PitEnabled *bool `json:",omitempty"` + State *string `json:",omitempty"` + UpdatedDate *string `json:",omitempty"` + UpdatedUser *string `json:",omitempty"` + PolicyItemHourly *ScheduledPolicyItem `json:",omitempty"` + PolicyItemDaily *ScheduledPolicyItem `json:",omitempty"` + PolicyItemWeekly []ScheduledPolicyItem `json:",omitempty"` + PolicyItemMonthly []ScheduledPolicyItem `json:",omitempty"` + PolicyItemYearly []ScheduledPolicyItem `json:",omitempty"` +} + +// OnDemandPolicyItem is autogenerated from the json schema +type OnDemandPolicyItem struct { + Id *string `json:",omitempty"` + FrequencyInterval *int `json:",omitempty"` + FrequencyType *string `json:",omitempty"` + RetentionUnit *string `json:",omitempty"` + RetentionValue *int `json:",omitempty"` +} + +// ScheduledPolicyItem is autogenerated from the json schema +type ScheduledPolicyItem struct { + Id *string `json:",omitempty"` + FrequencyType *string `json:",omitempty"` + FrequencyInterval *int `json:",omitempty"` + RetentionUnit *string `json:",omitempty"` + RetentionValue *int `json:",omitempty"` +} diff --git a/cfn-resources/backup-compliance-policy/cmd/resource/resource.go b/cfn-resources/backup-compliance-policy/cmd/resource/resource.go new file mode 100644 index 000000000..77789d6b4 --- /dev/null +++ b/cfn-resources/backup-compliance-policy/cmd/resource/resource.go @@ -0,0 +1,84 @@ +// Copyright 2026 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package resource + +import ( + "github.com/aws-cloudformation/cloudformation-cli-go-plugin/cfn/handler" + + "github.com/mongodb/mongodbatlas-cloudformation-resources/util" + "github.com/mongodb/mongodbatlas-cloudformation-resources/util/constants" + "github.com/mongodb/mongodbatlas-cloudformation-resources/util/validator" +) + +const callBackSeconds = 10 + +var ( + createRequiredFields = []string{constants.ProjectID, constants.AuthorizedEmail, constants.AuthorizedUserFirstName, constants.AuthorizedUserLastName} + readUpdateDeleteRequiredFields = []string{constants.ProjectID} + listRequiredFields = []string{constants.ProjectID} +) + +func Create(req handler.Request, prevModel *Model, model *Model) (handler.ProgressEvent, error) { + client, setupErr := setupRequest(req, model, createRequiredFields) + if setupErr != nil { + return *setupErr, nil + } + return HandleCreate(&req, client, model), nil +} + +func Read(req handler.Request, prevModel *Model, model *Model) (handler.ProgressEvent, error) { + client, setupErr := setupRequest(req, model, readUpdateDeleteRequiredFields) + if setupErr != nil { + return *setupErr, nil + } + return HandleRead(&req, client, model), nil +} + +func Update(req handler.Request, prevModel *Model, model *Model) (handler.ProgressEvent, error) { + client, setupErr := setupRequest(req, model, readUpdateDeleteRequiredFields) + if setupErr != nil { + return *setupErr, nil + } + return HandleUpdate(&req, client, model), nil +} + +func Delete(req handler.Request, prevModel *Model, model *Model) (handler.ProgressEvent, error) { + client, setupErr := setupRequest(req, model, readUpdateDeleteRequiredFields) + if setupErr != nil { + return *setupErr, nil + } + return HandleDelete(&req, client, model), nil +} + +func List(req handler.Request, prevModel *Model, model *Model) (handler.ProgressEvent, error) { + client, setupErr := setupRequest(req, model, listRequiredFields) + if setupErr != nil { + return *setupErr, nil + } + return HandleList(&req, client, model), nil +} + +func setupRequest(req handler.Request, model *Model, requiredFields []string) (*util.MongoDBClient, *handler.ProgressEvent) { + util.SetupLogger("mongodb-atlas-backup-compliance-policy") + if modelValidation := validator.ValidateModel(requiredFields, model); modelValidation != nil { + return nil, modelValidation + } + util.SetDefaultProfileIfNotDefined(&model.Profile) + client, progressEventErr := util.NewAtlasClient(&req, model.Profile) + if progressEventErr != nil { + return nil, progressEventErr + } + return client, nil +} diff --git a/cfn-resources/backup-compliance-policy/docs/README.md b/cfn-resources/backup-compliance-policy/docs/README.md new file mode 100644 index 000000000..9ded45e5b --- /dev/null +++ b/cfn-resources/backup-compliance-policy/docs/README.md @@ -0,0 +1,280 @@ +# MongoDB::Atlas::BackupCompliancePolicy + +Resource for managing MongoDB Atlas Backup Compliance Policy. Backup Compliance Policy prevents any user, regardless of role, from modifying or deleting specific cluster settings, backups, and backup configurations. When enabled, the Backup Compliance Policy will be applied as the minimum policy for all clusters and backups in the project. + +## Syntax + +To declare this entity in your AWS CloudFormation template, use the following syntax: + +### JSON + +
+{
+ "Type" : "MongoDB::Atlas::BackupCompliancePolicy",
+ "Properties" : {
+ "Profile" : String,
+ "ProjectId" : String,
+ "AuthorizedEmail" : String,
+ "AuthorizedUserFirstName" : String,
+ "AuthorizedUserLastName" : String,
+ "CopyProtectionEnabled" : Boolean,
+ "EncryptionAtRestEnabled" : Boolean,
+ "RestoreWindowDays" : Integer,
+ "OnDemandPolicyItem" : OnDemandPolicyItem,
+ "PitEnabled" : Boolean,
+ "PolicyItemHourly" : ScheduledPolicyItem,
+ "PolicyItemDaily" : ScheduledPolicyItem,
+ "PolicyItemWeekly" : [ ScheduledPolicyItem, ... ],
+ "PolicyItemMonthly" : [ ScheduledPolicyItem, ... ],
+ "PolicyItemYearly" : [ ScheduledPolicyItem, ... ]
+ }
+}
+
+
+### YAML
+
++Type: MongoDB::Atlas::BackupCompliancePolicy +Properties: + Profile: String + ProjectId: String + AuthorizedEmail: String + AuthorizedUserFirstName: String + AuthorizedUserLastName: String + CopyProtectionEnabled: Boolean + EncryptionAtRestEnabled: Boolean + RestoreWindowDays: Integer + OnDemandPolicyItem: OnDemandPolicyItem + PitEnabled: Boolean + PolicyItemHourly: ScheduledPolicyItem + PolicyItemDaily: ScheduledPolicyItem + PolicyItemWeekly: + - ScheduledPolicyItem + PolicyItemMonthly: + - ScheduledPolicyItem + PolicyItemYearly: + - ScheduledPolicyItem ++ +## Properties + +#### Profile + +Profile used to provide credentials information, (a secret with the cfn/atlas/profile/{Profile}, is required), if not provided default is used + +_Required_: No + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### ProjectId + +The unique identifier of the project for the Backup Compliance Policy. + +_Required_: Yes + +_Type_: String + +_Update requires_: [Replacement](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-replacement) + +#### AuthorizedEmail + +Email address of the user authorized to update the Backup Compliance Policy settings. + +_Required_: Yes + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### AuthorizedUserFirstName + +First name of the user authorized to update the Backup Compliance Policy settings. + +_Required_: Yes + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### AuthorizedUserLastName + +Last name of the user authorized to update the Backup Compliance Policy settings. + +_Required_: Yes + +_Type_: String + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### CopyProtectionEnabled + +Flag that indicates whether to enable additional copy protection for the cluster. If enabled, cloud backup snapshots cannot be deleted until the retention period expires. Defaults to false if not specified. + +_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) + +#### EncryptionAtRestEnabled + +Flag that indicates whether Encryption at Rest using Customer Key Management is required for all clusters with a Backup Compliance Policy. Defaults to false if not specified. + +_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) + +#### RestoreWindowDays + +Number of days back in time you can restore to with Continuous Cloud Backup accuracy. Must be a positive, non-zero integer. This field is optional and computed from the API. + +_Required_: No + +_Type_: Integer + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### OnDemandPolicyItem + +On-demand backup policy item configuration. When provided, FrequencyInterval, RetentionUnit, and RetentionValue are required. + +_Required_: No + +_Type_: OnDemandPolicyItem + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### PitEnabled + +Flag that indicates whether the cluster uses Continuous Cloud Backup. If enabled, cloud backup snapshots are taken every 6 hours. Defaults to false if not specified. + +_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) + +#### PolicyItemHourly + +Scheduled backup policy item configuration (hourly, daily, weekly, monthly, or yearly). When provided, FrequencyInterval, RetentionUnit, and RetentionValue are required. + +_Required_: No + +_Type_: ScheduledPolicyItem + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### PolicyItemDaily + +_Required_: No + +_Type_: ScheduledPolicyItem + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### PolicyItemWeekly + +Weekly backup policy item configuration. Multiple weekly policy items are allowed. + +_Required_: No + +_Type_: List of ScheduledPolicyItem + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### PolicyItemMonthly + +Monthly backup policy item configuration. Multiple monthly policy items are allowed. + +_Required_: No + +_Type_: List of ScheduledPolicyItem + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### PolicyItemYearly + +Yearly backup policy item configuration. Multiple yearly policy items are allowed. + +_Required_: No + +_Type_: List of ScheduledPolicyItem + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +## Return Values + +### Ref + +When you pass the logical ID of this resource to the intrinsic `Ref` function, Ref returns the ProjectId. + +### Fn::GetAtt + +The `Fn::GetAtt` intrinsic function returns a value for a specified attribute of this type. The following are the available attributes and sample return values. + +For more information about using the `Fn::GetAtt` intrinsic function, see [Fn::GetAtt](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getatt.html). + +#### State + +Current state of the Backup Compliance Policy settings. Possible values are: ACTIVE, INACTIVE. + +#### UpdatedDate + +ISO 8601 timestamp in UTC of when the Backup Compliance Policy settings were last updated. + +#### UpdatedUser + +Email address of the user who last updated the Backup Compliance Policy settings. + +#### OnDemandPolicyItem.Id + +Unique identifier of the on-demand backup policy item. + +#### OnDemandPolicyItem.FrequencyType + +Frequency type of the on-demand backup policy item. Always returns "ondemand". + +#### PolicyItemHourly.Id + +Unique identifier of the hourly backup policy item. + +#### PolicyItemHourly.FrequencyType + +Frequency type of the hourly backup policy item. Always returns "hourly". + +#### PolicyItemDaily.Id + +Unique identifier of the daily backup policy item. + +#### PolicyItemDaily.FrequencyType + +Frequency type of the daily backup policy item. Always returns "daily". + +#### PolicyItemWeekly.Id + +Unique identifier of the weekly backup policy item(s). For arrays, use index notation (e.g., PolicyItemWeekly[0].Id). + +#### PolicyItemWeekly.FrequencyType + +Frequency type of the weekly backup policy item(s). Always returns "weekly". For arrays, use index notation (e.g., PolicyItemWeekly[0].FrequencyType). + +#### PolicyItemMonthly.Id + +Unique identifier of the monthly backup policy item(s). For arrays, use index notation (e.g., PolicyItemMonthly[0].Id). + +#### PolicyItemMonthly.FrequencyType + +Frequency type of the monthly backup policy item(s). Always returns "monthly". For arrays, use index notation (e.g., PolicyItemMonthly[0].FrequencyType). + +#### PolicyItemYearly.Id + +Unique identifier of the yearly backup policy item(s). For arrays, use index notation (e.g., PolicyItemYearly[0].Id). + +#### PolicyItemYearly.FrequencyType + +Frequency type of the yearly backup policy item(s). Always returns "yearly". For arrays, use index notation (e.g., PolicyItemYearly[0].FrequencyType). + diff --git a/cfn-resources/backup-compliance-policy/docs/ondemandpolicyitem.md b/cfn-resources/backup-compliance-policy/docs/ondemandpolicyitem.md new file mode 100644 index 000000000..50d133302 --- /dev/null +++ b/cfn-resources/backup-compliance-policy/docs/ondemandpolicyitem.md @@ -0,0 +1,60 @@ +# MongoDB::Atlas::BackupCompliancePolicy OnDemandPolicyItem + +On-demand backup policy item configuration. When provided, FrequencyInterval, RetentionUnit, and RetentionValue are required. + +## Syntax + +To declare this entity in your AWS CloudFormation template, use the following syntax: + +### JSON + +
+{
+ "FrequencyInterval" : Integer,
+ "RetentionUnit" : String,
+ "RetentionValue" : Integer
+}
+
+
+### YAML
+
++FrequencyInterval: Integer +RetentionUnit: String +RetentionValue: Integer ++ +## Properties + +#### FrequencyInterval + +Number that indicates the frequency interval for a set of snapshots. Required when OnDemandPolicyItem is provided. + +_Required_: No + +_Type_: Integer + +_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt) + +#### RetentionUnit + +Unit of time in which MongoDB Cloud measures snapshot retention. Required when OnDemandPolicyItem is provided. + +_Required_: No + +_Type_: String + +_Allowed Values_:
days | weeks | months | years
+
+_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)
+
+#### RetentionValue
+
+Duration in days, weeks, months, or years that MongoDB Cloud retains the snapshot. Required when OnDemandPolicyItem is provided.
+
+_Required_: No
+
+_Type_: Integer
+
+_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)
+
diff --git a/cfn-resources/backup-compliance-policy/docs/scheduledpolicyitem.md b/cfn-resources/backup-compliance-policy/docs/scheduledpolicyitem.md
new file mode 100644
index 000000000..ec61d78be
--- /dev/null
+++ b/cfn-resources/backup-compliance-policy/docs/scheduledpolicyitem.md
@@ -0,0 +1,86 @@
+# MongoDB::Atlas::BackupCompliancePolicy ScheduledPolicyItem
+
+Scheduled backup policy item configuration (hourly, daily, weekly, monthly, or yearly). When provided, FrequencyInterval, RetentionUnit, and RetentionValue are required.
+
+## Syntax
+
+To declare this entity in your AWS CloudFormation template, use the following syntax:
+
+### JSON
+
+
+{
+ "Id" : String,
+ "FrequencyType" : String,
+ "FrequencyInterval" : Integer,
+ "RetentionUnit" : String,
+ "RetentionValue" : Integer
+}
+
+
+### YAML
+
++Id: String +FrequencyType: String +FrequencyInterval: Integer +RetentionUnit: String +RetentionValue: Integer ++ +## Properties + +#### Id + +Unique identifier of the backup policy item. + +_Required_: No + +_Type_: String + +_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)
+
+#### FrequencyType
+
+Frequency associated with the backup policy item. One of the following values: hourly, daily, weekly, monthly, or yearly. This is a read-only value.
+
+_Required_: No
+
+_Type_: String
+
+_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)
+
+#### FrequencyInterval
+
+Desired frequency of the new backup policy item specified by frequencyType. Required when the policy item is provided.
+
+_Required_: No
+
+_Type_: Integer
+
+_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)
+
+#### RetentionUnit
+
+Unit of time in which MongoDB Cloud measures snapshot retention. Required when the policy item is provided.
+
+_Required_: No
+
+_Type_: String
+
+_Allowed Values_: days | weeks | months | years
+
+_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)
+
+#### RetentionValue
+
+Duration in days, weeks, months, or years that MongoDB Cloud retains the snapshot. Required when the policy item is provided. For less frequent policy items, MongoDB Cloud requires that you specify a value greater than or equal to the value specified for more frequent policy items.
+
+_Required_: No
+
+_Type_: Integer
+
+_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)
+
diff --git a/cfn-resources/backup-compliance-policy/mongodb-atlas-backupcompliancepolicy.json b/cfn-resources/backup-compliance-policy/mongodb-atlas-backupcompliancepolicy.json
new file mode 100644
index 000000000..a3d452359
--- /dev/null
+++ b/cfn-resources/backup-compliance-policy/mongodb-atlas-backupcompliancepolicy.json
@@ -0,0 +1,203 @@
+{
+ "typeName": "MongoDB::Atlas::BackupCompliancePolicy",
+ "description": "Resource for managing MongoDB Atlas Backup Compliance Policy. Backup Compliance Policy prevents any user, regardless of role, from modifying or deleting specific cluster settings, backups, and backup configurations. When enabled, the Backup Compliance Policy will be applied as the minimum policy for all clusters and backups in the project.",
+ "definitions": {
+ "OnDemandPolicyItem": {
+ "type": "object",
+ "description": "On-demand backup policy item configuration. When provided, FrequencyInterval, RetentionUnit, and RetentionValue are required.",
+ "properties": {
+ "Id": {
+ "description": "Unique identifier of the backup policy item.",
+ "type": "string",
+ "pattern": "^([a-f0-9]{24})$"
+ },
+ "FrequencyInterval": {
+ "description": "Number that indicates the frequency interval for a set of snapshots. Required when OnDemandPolicyItem is provided.",
+ "type": "integer"
+ },
+ "FrequencyType": {
+ "description": "Frequency associated with the backup policy item. For on-demand policies, the frequency type is 'ondemand'. This is a read-only value.",
+ "type": "string"
+ },
+ "RetentionUnit": {
+ "description": "Unit of time in which MongoDB Cloud measures snapshot retention. Required when OnDemandPolicyItem is provided.",
+ "type": "string",
+ "enum": ["days", "weeks", "months", "years"]
+ },
+ "RetentionValue": {
+ "description": "Duration in days, weeks, months, or years that MongoDB Cloud retains the snapshot. Required when OnDemandPolicyItem is provided.",
+ "type": "integer"
+ }
+ },
+ "additionalProperties": false
+ },
+ "ScheduledPolicyItem": {
+ "type": "object",
+ "description": "Scheduled backup policy item configuration (hourly, daily, weekly, monthly, or yearly). When provided, FrequencyInterval, RetentionUnit, and RetentionValue are required.",
+ "properties": {
+ "Id": {
+ "description": "Unique identifier of the backup policy item.",
+ "type": "string",
+ "pattern": "^([a-f0-9]{24})$"
+ },
+ "FrequencyType": {
+ "description": "Frequency associated with the backup policy item. One of the following values: hourly, daily, weekly, monthly, or yearly. This is a read-only value.",
+ "type": "string"
+ },
+ "FrequencyInterval": {
+ "description": "Desired frequency of the new backup policy item specified by frequencyType. Required when the policy item is provided.",
+ "type": "integer"
+ },
+ "RetentionUnit": {
+ "description": "Unit of time in which MongoDB Cloud measures snapshot retention. Required when the policy item is provided.",
+ "type": "string",
+ "enum": ["days", "weeks", "months", "years"]
+ },
+ "RetentionValue": {
+ "description": "Duration in days, weeks, months, or years that MongoDB Cloud retains the snapshot. Required when the policy item is provided. For less frequent policy items, MongoDB Cloud requires that you specify a value greater than or equal to the value specified for more frequent policy items.",
+ "type": "integer"
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "properties": {
+ "Profile": {
+ "description": "Profile used to provide credentials information, (a secret with the cfn/atlas/profile/{Profile}, is required), if not provided default is used",
+ "type": "string",
+ "default": "default"
+ },
+ "ProjectId": {
+ "description": "The unique identifier of the project for the Backup Compliance Policy.",
+ "type": "string"
+ },
+ "AuthorizedEmail": {
+ "description": "Email address of the user authorized to update the Backup Compliance Policy settings.",
+ "type": "string"
+ },
+ "AuthorizedUserFirstName": {
+ "description": "First name of the user authorized to update the Backup Compliance Policy settings.",
+ "type": "string"
+ },
+ "AuthorizedUserLastName": {
+ "description": "Last name of the user authorized to update the Backup Compliance Policy settings.",
+ "type": "string"
+ },
+ "CopyProtectionEnabled": {
+ "description": "Flag that indicates whether to enable additional copy protection for the cluster. If enabled, cloud backup snapshots cannot be deleted until the retention period expires. Defaults to false if not specified.",
+ "type": "boolean"
+ },
+ "EncryptionAtRestEnabled": {
+ "description": "Flag that indicates whether Encryption at Rest using Customer Key Management is required for all clusters with a Backup Compliance Policy. Defaults to false if not specified.",
+ "type": "boolean"
+ },
+ "RestoreWindowDays": {
+ "description": "Number of days back in time you can restore to with Continuous Cloud Backup accuracy. Must be a positive, non-zero integer. This field is optional and computed from the API.",
+ "type": "integer"
+ },
+ "OnDemandPolicyItem": {
+ "description": "On-demand backup policy item configuration.",
+ "$ref": "#/definitions/OnDemandPolicyItem",
+ "type": "object"
+ },
+ "PitEnabled": {
+ "description": "Flag that indicates whether the cluster uses Continuous Cloud Backup. If enabled, cloud backup snapshots are taken every 6 hours. Defaults to false if not specified.",
+ "type": "boolean"
+ },
+ "State": {
+ "description": "Current state of the Backup Compliance Policy settings. Possible values are: ACTIVE, INACTIVE.",
+ "type": "string"
+ },
+ "UpdatedDate": {
+ "description": "ISO 8601 timestamp in UTC of when the Backup Compliance Policy settings were last updated.",
+ "type": "string",
+ "format": "date-time"
+ },
+ "UpdatedUser": {
+ "description": "Email address of the user who last updated the Backup Compliance Policy settings.",
+ "type": "string"
+ },
+ "PolicyItemHourly": {
+ "description": "Hourly backup policy item configuration. Only one hourly policy item is allowed.",
+ "$ref": "#/definitions/ScheduledPolicyItem",
+ "type": "object"
+ },
+ "PolicyItemDaily": {
+ "description": "Daily backup policy item configuration. Only one daily policy item is allowed.",
+ "$ref": "#/definitions/ScheduledPolicyItem",
+ "type": "object"
+ },
+ "PolicyItemWeekly": {
+ "description": "Weekly backup policy item configuration. Multiple weekly policy items are allowed.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ScheduledPolicyItem"
+ },
+ "insertionOrder": false
+ },
+ "PolicyItemMonthly": {
+ "description": "Monthly backup policy item configuration. Multiple monthly policy items are allowed.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ScheduledPolicyItem"
+ },
+ "insertionOrder": false
+ },
+ "PolicyItemYearly": {
+ "description": "Yearly backup policy item configuration. Multiple yearly policy items are allowed.",
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/ScheduledPolicyItem"
+ },
+ "insertionOrder": false
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "ProjectId",
+ "AuthorizedEmail",
+ "AuthorizedUserFirstName",
+ "AuthorizedUserLastName"
+ ],
+ "readOnlyProperties": [
+ "/properties/State",
+ "/properties/UpdatedDate",
+ "/properties/UpdatedUser",
+ "/properties/OnDemandPolicyItem/Id",
+ "/properties/OnDemandPolicyItem/FrequencyType",
+ "/properties/PolicyItemHourly/Id",
+ "/properties/PolicyItemHourly/FrequencyType",
+ "/properties/PolicyItemDaily/Id",
+ "/properties/PolicyItemDaily/FrequencyType",
+ "/properties/PolicyItemWeekly/*/Id",
+ "/properties/PolicyItemWeekly/*/FrequencyType",
+ "/properties/PolicyItemMonthly/*/Id",
+ "/properties/PolicyItemMonthly/*/FrequencyType",
+ "/properties/PolicyItemYearly/*/Id",
+ "/properties/PolicyItemYearly/*/FrequencyType"
+ ],
+ "createOnlyProperties": ["/properties/ProjectId"],
+ "primaryIdentifier": ["/properties/ProjectId"],
+ "handlers": {
+ "create": {
+ "permissions": ["secretsmanager:GetSecretValue"]
+ },
+ "read": {
+ "permissions": ["secretsmanager:GetSecretValue"]
+ },
+ "update": {
+ "permissions": ["secretsmanager:GetSecretValue"]
+ },
+ "delete": {
+ "permissions": ["secretsmanager:GetSecretValue"]
+ },
+ "list": {
+ "permissions": ["secretsmanager:GetSecretValue"]
+ }
+ },
+ "documentationUrl": "https://github.com/mongodb/mongodbatlas-cloudformation-resources/blob/master/cfn-resources/backup-compliance-policy/README.md",
+ "tagging": {
+ "taggable": false
+ },
+ "sourceUrl": "https://github.com/mongodb/mongodbatlas-cloudformation-resources/tree/master/cfn-resources/backup-compliance-policy"
+}
diff --git a/cfn-resources/backup-compliance-policy/resource-role.yaml b/cfn-resources/backup-compliance-policy/resource-role.yaml
new file mode 100644
index 000000000..546fb85f1
--- /dev/null
+++ b/cfn-resources/backup-compliance-policy/resource-role.yaml
@@ -0,0 +1,38 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Description: >
+ This CloudFormation template creates a role assumed by CloudFormation
+ during CRUDL operations to mutate resources on behalf of the customer.
+
+Resources:
+ ExecutionRole:
+ Type: AWS::IAM::Role
+ Properties:
+ MaxSessionDuration: 8400
+ AssumeRolePolicyDocument:
+ 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-BackupCompliancePolicy/*
+ Path: "/"
+ Policies:
+ - PolicyName: ResourceTypePolicy
+ PolicyDocument:
+ Version: '2012-10-17'
+ Statement:
+ - Effect: Allow
+ Action:
+ - "secretsmanager:GetSecretValue"
+ Resource: "*"
+Outputs:
+ ExecutionRoleArn:
+ Value:
+ Fn::GetAtt: ExecutionRole.Arn
diff --git a/cfn-resources/backup-compliance-policy/template.yml b/cfn-resources/backup-compliance-policy/template.yml
new file mode 100644
index 000000000..3329b174e
--- /dev/null
+++ b/cfn-resources/backup-compliance-policy/template.yml
@@ -0,0 +1,27 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Transform: AWS::Serverless-2016-10-31
+Description: AWS SAM template for the MongoDB::Atlas::BackupCompliancePolicy resource type
+
+Globals:
+ Function:
+ Timeout: 180 # docker start-up times can be long for SAM CLI
+ MemorySize: 256
+
+Resources:
+ TypeFunction:
+ Type: AWS::Serverless::Function
+ Properties:
+ Handler: bootstrap
+ Runtime: provided.al2
+ CodeUri: bin/
+
+ TestEntrypoint:
+ Type: AWS::Serverless::Function
+ Properties:
+ Handler: bootstrap
+ Runtime: provided.al2
+ CodeUri: bin/
+ Environment:
+ Variables:
+ MODE: Test
+ LOG_LEVEL: debug
diff --git a/cfn-resources/backup-compliance-policy/test/backup-compliance-policy.sample-cfn-request.json b/cfn-resources/backup-compliance-policy/test/backup-compliance-policy.sample-cfn-request.json
new file mode 100644
index 000000000..ab15bb805
--- /dev/null
+++ b/cfn-resources/backup-compliance-policy/test/backup-compliance-policy.sample-cfn-request.json
@@ -0,0 +1,13 @@
+{
+ "desiredResourceState": {
+ "ProjectId": "