diff --git a/.github/workflows/deploy-cloudformation-stackset.yml b/.github/workflows/deploy-cloudformation-stackset.yml new file mode 100644 index 00000000..2adfcb32 --- /dev/null +++ b/.github/workflows/deploy-cloudformation-stackset.yml @@ -0,0 +1,188 @@ +--- +name: Cloudformation stack-set & stack-set-instances +on: + workflow_call: + inputs: + aws-region: + description: 'Aws region (in this region stackset enabled)' + required: false + default: 'us-east-2' + type: string + stackset-instance-region: + description: 'Stackset-instance regions where you need cloudformation stacks' + required: false + default: 'us-east-2' + type: string + stack-set-name: + description: 'Stack-set name defined here' + required: true + type: string + template-url: + description: 'Cloudformation template path add here (S3 Object URL)' + required: true + type: string + OrganizationalUnitIds: + description: 'Organization unit ID for deployment in target accounts when service_managed permission added' + required: false + type: string + account-ids: + description: 'account ids for self_managed permission added' + required: false + type: string + parameter-overrides: + description: 'The parameters to override in the stack inputs. You can pass a comma-delimited list or a file URL. The comma-delimited list has each entry formatted as = or =",".' + required: false + type: string + permission-model: + description: 'IAM role permission SERVICE_MANAGED/SELF_MANAGED choose one' + required: false + type: string + auto-deployment-enabled: + description: 'true or false (true when Service_managed policy enable else false for Self_managed)' + required: true + type: string + RetainStacksOnAccountRemoval: + description: 'true or false (true when Service_managed policy enable else false for Self_managed)' + required: true + type: string + administration-role-arn: + description: 'Administrator role arn add here for trust relation on admin and child account' + required: false + type: string + execution-role-name: + description: 'execution-role-name add here for trust relation in child account' + required: false + type: string + secrets: + AWS_ACCESS_KEY_ID: + required: false + description: 'AWS Access Key ID to install AWS CLI.' + AWS_SECRET_ACCESS_KEY: + required: false + description: 'AWS Secret access key to install AWS CLI' + AWS_SESSION_TOKEN: + required: false + description: 'AWS Session Token to install AWS CLI' + AWS_ROLE_TO_ASSUME: + required: false + description: 'AWS Role ARN defined' + GITHUB: + required: false + description: 'GitHub token' + +jobs: + deploy-cf-stackset: + runs-on: ubuntu-latest + steps: + - name: Checkout code from master branch + uses: actions/checkout@v4 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID}} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-session-token: ${{ secrets.AWS_SESSION_TOKEN }} + aws-region: ${{ inputs.aws-region }} + role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} + + - name: Check if StackSet exists or not-exist then create/update stack-set + id: check-stackset + run: | + set +e + result=$(aws cloudformation describe-stack-set --stack-set-name "${{ inputs.stack-set-name }}" 2>&1) + RC=$? + set -e + if [ "${{ inputs.permission-model }}" = "SERVICE_MANAGED" ]; then + if [ $RC -eq 0 ]; then + echo "StackSet exists, updating..." + aws cloudformation update-stack-set \ + --stack-set-name ${{ inputs.stack-set-name }} \ + --template-url ${{ inputs.template-url }} \ + --parameters ${{ inputs.parameter-overrides }} \ + --capabilities CAPABILITY_NAMED_IAM \ + --permission-model ${{ inputs.permission-model }} \ + --auto-deployment Enabled=${{ inputs.auto-deployment-enabled }},RetainStacksOnAccountRemoval=${{ inputs.RetainStacksOnAccountRemoval }} + elif [ $RC -eq 254 ]; then + if echo "$result" | grep -q "StackSetNotFoundException"; then + echo "StackSet does not exist, creating..." + aws cloudformation create-stack-set \ + --stack-set-name ${{ inputs.stack-set-name }} \ + --template-url ${{ inputs.template-url }} \ + --parameters ${{ inputs.parameter-overrides }} \ + --capabilities CAPABILITY_NAMED_IAM \ + --permission-model ${{ inputs.permission-model }} \ + --auto-deployment Enabled=${{ inputs.auto-deployment-enabled }},RetainStacksOnAccountRemoval=${{ inputs.RetainStacksOnAccountRemoval }} + else + exit $RC + fi + else + exit $RC + fi + else + if [ $RC -eq 0 ]; then + echo "StackSet exists, updating..." + aws cloudformation update-stack-set \ + --stack-set-name ${{ inputs.stack-set-name }} \ + --template-url ${{ inputs.template-url }} \ + --capabilities CAPABILITY_NAMED_IAM \ + --parameters ${{ inputs.parameter-overrides }} \ + --administration-role-arn ${{ inputs.administration-role-arn }} + elif [ $RC -eq 254 ]; then + if echo "$result" | grep -q "StackSetNotFoundException"; then + echo "StackSet does not exist, creating..." + aws cloudformation create-stack-set \ + --stack-set-name ${{ inputs.stack-set-name }} \ + --template-url ${{ inputs.template-url }} \ + --capabilities CAPABILITY_NAMED_IAM \ + --parameters ${{ inputs.parameter-overrides }} \ + --administration-role-arn ${{ inputs.administration-role-arn }} \ + --execution-role-name AWSControlTowerExecution + else + exit $RC + fi + else + exit $RC + fi + sleep 50s + fi + + - name: Create or Update StackSet-instance + run: | + stack_instance_list=$(aws cloudformation list-stack-instances --region ${{ inputs.stackset-instance-region }} --stack-set-name ${{ inputs.stack-set-name }}) + if [ "${{ inputs.permission-model }}" == "SERVICE_MANAGED" ]; then + if [[ "$stack_instance_list" == *'"Summaries": []'* ]]; then + echo "StackSet-instance, creating..." + aws cloudformation create-stack-instances \ + --stack-set-name ${{ inputs.stack-set-name }} \ + --deployment-targets OrganizationalUnitIds='["${{ inputs.OrganizationalUnitIds }}"]' \ + --parameter-overrides ${{ inputs.parameter-overrides }} \ + --regions ${{ inputs.stackset-instance-region }} + else + echo "StackSet-instance, updating..." + aws cloudformation update-stack-instances \ + --stack-set-name ${{ inputs.stack-set-name }} \ + --deployment-targets OrganizationalUnitIds='["${{ inputs.OrganizationalUnitIds }}"]' \ + --parameter-overrides ${{ inputs.parameter-overrides }} \ + --regions ${{ inputs.stackset-instance-region }} + fi + else + if [[ "$stack_instance_list" == *'"Summaries": []'* ]]; then + echo "StackSet-instance, creating..." + aws cloudformation create-stack-instances \ + --stack-set-name ${{ inputs.stack-set-name }} \ + --parameter-overrides ${{ inputs.parameter-overrides }} \ + --accounts ${{ inputs.account-ids }} \ + --regions ${{ inputs.stackset-instance-region }} \ + --operation-preferences FailureToleranceCount=1,MaxConcurrentCount=2 + else + echo "StackSet-instance, updating..." + aws cloudformation update-stack-instances \ + --stack-set-name ${{ inputs.stack-set-name }} \ + --parameter-overrides ${{ inputs.parameter-overrides }} \ + --accounts ${{ inputs.account-ids }} \ + --regions ${{ inputs.stackset-instance-region }} \ + --operation-preferences MaxConcurrentPercentage=1 + fi + fi +... diff --git a/.github/workflows/deploy-cloudformation.yml b/.github/workflows/deploy-cloudformation.yml index 07077d6d..b232bb55 100644 --- a/.github/workflows/deploy-cloudformation.yml +++ b/.github/workflows/deploy-cloudformation.yml @@ -52,6 +52,12 @@ on: description: 'The parameters to override in the stack inputs. You can pass a comma-delimited list or a file URL. The comma-delimited list has each entry formatted as = or =",".' required: false type: string + capabilities: + description: "The comma-delimited list of stack template capabilities to acknowledge. Defaults to 'CAPABILITY_IAM'" + required: false + default: "CAPABILITY_IAM" + type: string + secrets: AWS_ACCESS_KEY_ID: required: false @@ -107,5 +113,6 @@ jobs: name: ${{ inputs.stack-name }} template: ${{ inputs.template-path }} no-fail-on-empty-changeset: "1" - parameter-overrides: ${{ inputs.parameter-overrides}} + parameter-overrides: ${{ inputs.parameter-overrides }} + capabilities: ${{ inputs.capabilities }} ... diff --git a/README.md b/README.md index 92d69214..b1034190 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,8 @@ Above example is just a simple example to call workflow from github shared workf 7. [Checkov Workflow](https://github.com/clouddrove/github-shared-workflows/blob/master/docs/checkov.md) 8. [Terraform Workflow](https://github.com/clouddrove/github-shared-workflows/blob/master/docs/terraform_workflow.md) 9. [Infracost workflow](https://github.com/clouddrove/github-shared-workflows/blob/master/docs/infracost.md) -10. [ Deploy Cloudformation workflow](https://github.com/clouddrove/github-shared-workflows/blob/master/docs/deploy-cloudformation.md) +10. [ Deploy Cloudformation Stack workflow](https://github.com/clouddrove/github-shared-workflows/blob/master/docs/deploy-cloudformation.md) +11. [ Deploy Cloudformation Stackset workflow](https://github.com/clouddrove/github-shared-workflows/blob/master/docs/deploy-cloudformation-stackset.md) ## Feedback If you come accross a bug or have any feedback, please log it in our [issue tracker](https://github.com/clouddrove/github-shared-workflows/issues), or feel free to drop us an email at [hello@clouddrove.com](mailto:hello@clouddrove.com). diff --git a/docs/deploy-cloudformation-stackset.md b/docs/deploy-cloudformation-stackset.md new file mode 100644 index 00000000..2ab61067 --- /dev/null +++ b/docs/deploy-cloudformation-stackset.md @@ -0,0 +1,53 @@ +## [Deploy Cloudformation Stacket & Stackset-instances](https://github.com/clouddrove/github-shared-workflows/blob/master/.github/workflows/deploy-cloudformation-stackset.yml) + The process starts with the creation of a shared workflow template. This template contains CloudFormation resource definitions, parameter declarations, and other configuration settings that are commonly used across multiple projects or environments. It serves as a blueprint for the infrastructure you want to create. `.github/workflows/deploy-cloudformation-stackset.yml` + +#### Usage + +- In this workflow we added multiple parameters like S3 bucket for source code, stack-parameters, account-ids, stackset-name using parameters we overrides from called.yml as we defined below. +- In this workflow we provide S3 Object URL where your code & template file located and deploy stackset and stackset-instances +- Most important thing is we centrally manage stacks of every account's using stackset + +#### Key Points: + In this workflow we added steps like for the below conditions: + + - If stackset are not-Exists then Create a new **stackset** + - If stackset are Exist then Updating a **stackset** + - If stackset-instance is not-Exist then Create a new **stackset-instance** + - If stackset-instance is Exist then Updating a **stackset-instance** + +#### Example + +```yaml +name: Cloudformation stack-set +on: + push: + branches: main + workflow_dispatch: + +permissions: + id-token: write + contents: read + +jobs: + deploy-cf-stackset: + uses: clouddrove/github-shared-workflows/.github/workflows/deploy-cloudformation-stackset.yml@master + with: + aws-region: # aws-configure region add, where you need stackset + stackset-instance-region: # region add where you need stacks + stack-set-name: # name of stack-set ( same name apply for stackset & instances ) + template-url: # S3 bucket Object URL add where template file is located + OrganizationalUnitIds: "" # deployment targets OrganizationalUnitIds + account-ids: # deployment targets add master account ids where you deploying stacksets + parameter-overrides: # use this format (ParameterKey=ABC,ParameterValue=XXX ParameterKey=XYZ,ParameterValue=XXX) + permission-model: # SELF_MANAGED & SERVICE_MANAGED add here + auto-deployment-enabled: false # for SELF_MANAGED-false & SERVICE_MANAGED-true + RetainStacksOnAccountRemoval: false # for SELF_MANAGED-false & SERVICE_MANAGED-true + administration-role-arn: # administration AWSControlTowerStackSetRole ARN add here + execution-role-name: # child account AWSControlTowerExecution role name add here + + secrets: + AWS_ROLE_TO_ASSUME: # Add AWS OIDC role ARN + AWS_ACCESS_KEY_ID: # Add AWS credentials + AWS_SECRET_ACCESS_KEY: + AWS_SESSION_TOKEN: +``` \ No newline at end of file diff --git a/docs/deploy-cloudformation.md b/docs/deploy-cloudformation.md index de6777e0..ccb5cfc7 100644 --- a/docs/deploy-cloudformation.md +++ b/docs/deploy-cloudformation.md @@ -1,4 +1,4 @@ -## [Deploy Cloudformation Stack](https://github.com/clouddrove/github-shared-workflows/blob/master/.github/workflows/infracost.yml) +## [Deploy Cloudformation Stack](https://github.com/clouddrove/github-shared-workflows/blob/master/.github/workflows/deploy-cloudformation.yml) The process starts with the creation of a shared workflow template. This template contains CloudFormation resource definitions, parameter declarations, and other configuration settings that are commonly used across multiple projects or environments. It serves as a blueprint for the infrastructure you want to create. `.github/workflows/deploy-cloudformation.yml` #### Usage