Description: AWS CloudFormation Publisher packages your CloudFormation templates into an S3 bucket in every AWS region (uksb-1q29miumr) Resources: Role: Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: Statement: - Action: "sts:AssumeRole" Effect: Allow Principal: Service: - codebuild.amazonaws.com Version: "2012-10-17" ManagedPolicyArns: - "arn:aws:iam::aws:policy/AmazonS3FullAccess" Policies: - PolicyDocument: Statement: - Action: - "codecommit:GitPull" Effect: Allow Resource: - "*" Sid: CodeCommitPolicy - Action: - "ec2:DescribeRegions" Effect: Allow Resource: - "*" Sid: DiscoverRegionsPolicy - Action: - "sts:GetCallerIdentity" Effect: Allow Resource: - "*" Sid: GetCallerInfoPolicy - Action: - "logs:CreateLogGroup" - "logs:CreateLogStream" - "logs:PutLogEvents" Effect: Allow Resource: - "*" Sid: CloudWatchLogsPolicy - Action: - "ecr:BatchCheckLayerAvailability" - "ecr:BatchGetImage" - "ecr:GetDownloadUrlForLayer" Effect: Allow Resource: - "*" Sid: ECRPullPolicy - Action: - "ecr:GetAuthorizationToken" Effect: Allow Resource: - "*" Sid: ECRAuthPolicy Version: "2012-10-17" PolicyName: CodeBuild StatusTopic: Type: AWS::SNS::Topic Properties: DisplayName: cfn-publish EventTopicPolicy: Type: 'AWS::SNS::TopicPolicy' Properties: PolicyDocument: Statement: - Effect: Allow Principal: Service: events.amazonaws.com Action: 'sns:Publish' Resource: '*' Topics: - !Ref StatusTopic PublishEventRule: Type: AWS::Events::Rule Properties: Description: CFN Publish build status notifier EventPattern: source: - "aws.codebuild" detail-type: - "CodeBuild Build State Change" detail: project-name: - !Ref Project State: "ENABLED" Targets: - Arn: !Ref "StatusTopic" Id: "StatusTopic" InputTransformer: InputPathsMap: build-status: "$.detail.build-status" location: "$.detail.additional-information.source.location" InputTemplate: > "Publication of '' has reached the status ''" Project: Type: "AWS::CodeBuild::Project" Properties: Name: cfn-publish Artifacts: Type: NO_ARTIFACTS Environment: Type: LINUX_CONTAINER ComputeType: BUILD_GENERAL1_SMALL Image: "aws/codebuild/standard:3.0" ServiceRole: !GetAtt Role.Arn Source: Type: NO_SOURCE BuildSpec: | version: 0.2 phases: build: commands: - REPO_NAME=$(basename "$CODEBUILD_SOURCE_REPO_URL") - project="${REPO_NAME%.*}" - version="${VERSION:-latest}" - echo Building $project... - echo Getting account number... - account="$(aws sts get-caller-identity --output text --query Account)" - regions="" - bucket_name_prefix="cfn-$account" - acl="private" - extra_files="" - | if [ -f ./cfn-publish.config ]; then echo Reading config... . ./cfn-publish.config fi - | if [ -z "$regions" ]; then echo Discovering regions... regions=$(aws ec2 describe-regions --output text --query 'Regions[].RegionName') fi - # Handle "templates" or "template" variable - | if [ -z "$templates" ]; then if [ -n "$template" ]; then templates="$template" else templates="cfn.template" fi fi - | cat <<"EOL" >publish.sh #!/bin/bash set -e set -x # Loop through regions for region in $regions; do echo Publishing to "$region"... export AWS_DEFAULT_REGION="$region" export BUCKET="${bucket_name_prefix}-${region}" # Make the bucket if it doesn't exist aws s3api head-bucket --bucket "$BUCKET" 2>/dev/null || aws s3 mb "s3://$BUCKET" # Give the bucket time to existify aws s3api wait bucket-exists --bucket "$BUCKET" # Clear existing files echo Clearing existing files for version: "$version" aws s3 rm --recursive "s3://${BUCKET}/${project}/${version}" # Loop through templates for template in $templates; do # Package the template aws cloudformation package --template-file "$template" --s3-bucket "$BUCKET" --s3-prefix "${project}/${version}" --output-template-file packaged.template # Define paths path="${project}/${version}/$(basename $template)" # Copy it to the bucket aws s3 cp --acl "$acl" packaged.template "s3://${BUCKET}/${path}" # Sync to latest if [ "$version" != "latest" ]; then echo "Syncing $version to latest" aws s3 rm "s3://${BUCKET}/${project}/latest/$(basename $template)" aws s3 cp --acl "$acl" "s3://${BUCKET}/${path}" "s3://${BUCKET}/${project}/latest/$(basename $template)" fi done # Add extra files for extra_file in $extra_files; do aws s3 cp --acl "$acl" "$extra_file" "s3://${BUCKET}/${project}/${version}/${extra_file}" done # Set permissions of artefacts objects=$(aws s3 ls --recursive "s3://${BUCKET}/${project}/${version}") echo "$objects" | awk -v bucket="$BUCKET" -v acl="$acl" '{system("aws s3api put-object-acl --bucket " bucket " --key " $4 " --acl " acl)}' done # Create an index file in the first region's bucket for region in $regions; do first_region="$region" break done cat <index.html ${project}

${project}

EOF for template in $templates; do cat <>index.html EOF done cat <>index.html EOF for region in $regions; do export AWS_DEFAULT_REGION="${region}" export BUCKET="${bucket_name_prefix}-${region}" cat <>index.html EOF for template in $templates; do # Strip disallowed cfn stack name characters name=$(echo "${project}-${template%.*}" | sed -e 's/[^a-zA-Z0-9]\+/-/g') url="https://s3.amazonaws.com/${BUCKET}/${project}/${version}/$(basename $template)" cat <>index.html EOF done cat <>index.html EOF done # Close the index file cat <>index.html
Region$(basename $template)
${region} Launch Stack
EOF # Upload the website export AWS_DEFAULT_REGION="${first_region}" export BUCKET="${bucket_name_prefix}-${first_region}" aws s3api put-bucket-website --bucket "$BUCKET" --website-configuration '{"IndexDocument": {"Suffix": "index.html"}}' aws s3 cp --acl "$acl" index.html "s3://${BUCKET}/${project}/${version}.html" aws s3 cp --acl "$acl" index.html "s3://${BUCKET}/${project}/index.html" echo "http://${BUCKET}.s3-website-${first_region}.amazonaws.com/${project}/index.html" EOL - chmod +x publish.sh - . ./publish.sh