diff --git a/.jenkins/jenkins-ci-aws-codecommit.jenkinsfile b/.jenkins/jenkins-ci-aws-codecommit.jenkinsfile index 31441756..40603908 100644 --- a/.jenkins/jenkins-ci-aws-codecommit.jenkinsfile +++ b/.jenkins/jenkins-ci-aws-codecommit.jenkinsfile @@ -1,6 +1,6 @@ def PATH_KUBECONFIG = '/home/ubuntu/.kube/config' def ECR_REPO = '0987612345.dkr.ecr.ap-southeast-1.amazonaws.com/devopscorner/bookstore' -def VCS_REPO = 'ssh://git-codecommit.ap-southeast-1.amazonaws.com/v1/repos/bookstore' +def VCS_REPO = 'ssh://git-codecommit.ap-southeast-1.amazonaws.com/v1/repos/golang-deployment' def SPINNAKER_HOOK = 'https://spinnaker.awscb.id/webhooks/webhook/bookstore' def skipRemainingStages = false def nextVersionFromGit(scope) { diff --git a/CHANGELOG.md b/CHANGELOG.md index 948a513e..d960fe9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,70 @@ -## Changelog GO App +# Golang Deployment + +Kubernetes Deployment for Simple Golang API + +![goreport](https://goreportcard.com/badge/github.com/devopscorner/golang-deployment) ![all contributors](https://img.shields.io/github/contributors/devopscorner/golang-deployment) ![tags](https://img.shields.io/github/v/tag/devopscorner/golang-deployment?sort=semver) [![docker pulls](https://img.shields.io/docker/pulls/devopscorner/bookstore.svg)](https://hub.docker.com/r/devopscorner/bookstore/) ![download all](https://img.shields.io/github/downloads/devopscorner/golang-deployment/total.svg) ![download latest](https://img.shields.io/github/downloads/devopscorner/golang-deployment/3.1/total) ![view](https://views.whatilearened.today/views/github/devopscorner/golang-deployment.svg) ![clone](https://img.shields.io/badge/dynamic/json?color=success&label=clone&query=count&url=https://github.com/devopscorner/golang-deployment/blob/master/clone.json?raw=True&logo=github) ![issues](https://img.shields.io/github/issues/devopscorner/golang-deployment) ![pull requests](https://img.shields.io/github/issues-pr/devopscorner/golang-deployment) ![forks](https://img.shields.io/github/forks/devopscorner/golang-deployment) ![stars](https://img.shields.io/github/stars/devopscorner/golang-deployment) [![license](https://img.shields.io/github/license/devopscorner/golang-deployment)](https://img.shields.io/github/license/devopscorner/golang-deployment) + +## Available Tags + +### Alpine + +| Image name | Size | +|------------|------| +| `devopscorner/bookstore:latest` | [![docker image size](https://img.shields.io/docker/image-size/devopscorner/bookstore/latest.svg?label=Image%20size&logo=docker)](https://hub.docker.com/repository/docker/devopscorner/bookstore/tags?page=1&ordering=last_updated&name=latest) | +| `devopscorner/bookstore:alpine` | [![docker image size](https://img.shields.io/docker/image-size/devopscorner/bookstore/alpine.svg?label=Image%20size&logo=docker)](https://hub.docker.com/repository/docker/devopscorner/bookstore/tags?page=1&ordering=last_updated&name=alpine) | +| `devopscorner/bookstore:alpine-latest` | [![docker image size](https://img.shields.io/docker/image-size/devopscorner/bookstore/alpine-latest.svg?label=Image%20size&logo=docker)](https://hub.docker.com/repository/docker/devopscorner/bookstore/tags?page=1&ordering=last_updated&name=alpine-latest) | +| `devopscorner/bookstore:alpine-3.15` | [![docker image size](https://img.shields.io/docker/image-size/devopscorner/bookstore/alpine-3.15.svg?label=Image%20size&logo=docker)](https://hub.docker.com/repository/docker/devopscorner/bookstore/tags?page=1&ordering=last_updated&name=alpine-3.15) | +| `devopscorner/bookstore:go1.19-alpine3.15` | [![docker image size](https://img.shields.io/docker/image-size/devopscorner/bookstore/go1.19-alpine3.15.svg?label=Image%20size&logo=docker)](https://hub.docker.com/repository/docker/devopscorner/bookstore/tags?page=1&ordering=last_updated&name=go1.19-alpine3.15) | +| `devopscorner/bookstore:go1.19.3-alpine3.15` | [![docker image size](https://img.shields.io/docker/image-size/devopscorner/bookstore/go1.19.3-alpine3.15.svg?label=Image%20size&logo=docker)](https://hub.docker.com/repository/docker/devopscorner/bookstore/tags?page=1&ordering=last_updated&name=go1.19.3-alpine3.15) | +| `devopscorner/bookstore:alpine-3.16` | [![docker image size](https://img.shields.io/docker/image-size/devopscorner/bookstore/alpine-3.16.svg?label=Image%20size&logo=docker)](https://hub.docker.com/repository/docker/devopscorner/bookstore/tags?page=1&ordering=last_updated&name=alpine-3.16) | +| `devopscorner/bookstore:go1.19-alpine3.16` | [![docker image size](https://img.shields.io/docker/image-size/devopscorner/bookstore/go1.19-alpine3.16.svg?label=Image%20size&logo=docker)](https://hub.docker.com/repository/docker/devopscorner/bookstore/tags?page=1&ordering=last_updated&name=go1.19-alpine3.16) | +| `devopscorner/bookstore:go1.19.5-alpine3.16` | [![docker image size](https://img.shields.io/docker/image-size/devopscorner/bookstore/go1.19.5-alpine3.16.svg?label=Image%20size&logo=docker)](https://hub.docker.com/repository/docker/devopscorner/bookstore/tags?page=1&ordering=last_updated&name=go1.19.5-alpine3.16) | +| `devopscorner/bookstore:alpine-3.17` | [![docker image size](https://img.shields.io/docker/image-size/devopscorner/bookstore/alpine-3.17.svg?label=Image%20size&logo=docker)](https://hub.docker.com/repository/docker/devopscorner/bookstore/tags?page=1&ordering=last_updated&name=alpine-3.17) | +| `devopscorner/bookstore:go1.19-alpine3.17` | [![docker image size](https://img.shields.io/docker/image-size/devopscorner/bookstore/go1.19-alpine3.17.svg?label=Image%20size&logo=docker)](https://hub.docker.com/repository/docker/devopscorner/bookstore/tags?page=1&ordering=last_updated&name=go1.19-alpine3.17) | +| `devopscorner/bookstore:go1.19.5-alpine3.17` | [![docker image size](https://img.shields.io/docker/image-size/devopscorner/bookstore/go1.19.5-alpine3.17.svg?label=Image%20size&logo=docker)](https://hub.docker.com/repository/docker/devopscorner/bookstore/tags?page=1&ordering=last_updated&name=go1.19.5-alpine3.17) | + + +### Alpine (Depreciated) +| Image name | Size | +|------------|------| +| `devopscorner/bookstore:go1.18-alpine3.15` | [![docker image size](https://img.shields.io/docker/image-size/devopscorner/bookstore/go1.18-alpine3.15.svg?label=Image%20size&logo=docker)](https://hub.docker.com/repository/docker/devopscorner/bookstore/tags?page=1&ordering=last_updated&name=go1.18-alpine3.15) | +| `devopscorner/bookstore:go1.18-alpine3.16` | [![docker image size](https://img.shields.io/docker/image-size/devopscorner/bookstore/go1.18-alpine3.16.svg?label=Image%20size&logo=docker)](https://hub.docker.com/repository/docker/devopscorner/bookstore/tags?page=1&ordering=last_updated&name=go1.18-alpine3.16) | + +--- + +### version 3.3 + +- All features in version 3.2 +- Added Terraform script: + - GitHub, AWS CodeBuild, AWS CodePipeline & Amazon SNS + - AWS CodeCommit, AWS CodeBuild, AWS CodePipeline & Amazon SNS +- Added CloudFormation script: + - GitHub, AWS CodeBuild, AWS CodePipeline & Amazon SNS + - AWS CodeCommit, AWS CodeBuild, AWS CodePipeline & Amazon SNS + +--- + +### version 3.3 + +- All features in version 3.2 +- Deployment for CI/CD Pipeline: + - **ArgoCD**, detail [here](docs/deployment-argocd.md) link + - **AWS Developer Tools** (AWS CodeCommit, AWS CodeBuild & AWS CodePipeline), detail [here](docs/deployment-aws-developer-tools.md) link + - **Azure DevOps Pipeline**, detail [here](docs/deployment-azure-devops.md) link + - **Bitbucket Pipeline**, detail [here](docs/deployment-bitbucket.md) link + - **CircleCI Pipeline**, detail [here](docs/deployment-circleci.md) link + - **DroneCI Pipeline**, detail [here](docs/deployment-droneci.md) link + - **GitHub Action**, detail [here](docs/deployment-github.md) link + - **GitLab CI/CD**, detail [here](docs/deployment-gitlab.md) link + - **Jenkins CI & Spinnaker CD**, detail [here](docs/deployment-jenkins-spinnaker.md) link + - **Jenkins CI/CD**, detail [here](docs/deployment-jenkins.md) link + - **OpenShift CI/CD**, detail [here](docs/deployment-openshift.md) link + - **SemaphoreCI**, detail [here](docs/deployment-semaphoreci.md) link + - **Spinnaker CD**, detail [here](docs/deployment-spinnaker.md) link + - **Terraform AWS CodeBuild, AWS CodePipeline & Amazon SNS**, detail [here](docs/deployment-terraform.md) link + - **TravisCI**, detail [here](docs/deployment-travisci.md) link + +--- ### version 3.3 @@ -31,6 +97,8 @@ - GitLab (`cicd-gitlab.yml`) - Jenkins CI/CD (`cicd-jenkins.jenkinsfile`) +--- + ### version 3.1 - All features in version 3.0 @@ -51,6 +119,8 @@ - Refactoring build, tag, push & pull script for ECR - Refactoring `makefile` script automation for build, tag, push & pull +--- + ### version 3.0 - All features in version 2.3 @@ -61,11 +131,15 @@ - Refactoring source code (moving) dependencies to `devopscorner/golang-deployment` - Update `gorm` model & sqlite connection driver +--- + ### version 2.3 - All features in version 2.2 - Refactoring path & references docs +--- + ### version 2.2 - Add multiple container registry (DockerHub & ECR) deployment @@ -75,6 +149,8 @@ - Add documentation for build, tag & push container to **Amazon ECR (Elastic Container Registry)**, go to [this](docs/container-bookstore-ecr.md) link - Refactoring workflow documentation, go to [this](docs/workflow-cicd-bookstore-pipeline.md) link +--- + ### version 2.1 - Add Configuration Pipeline Synchronize for Mirroring Repository into AWS CodeCommit @@ -89,6 +165,8 @@ - Azure DevSecOps Pipeline ![Azure DevSecOps Pipeline](docs/assets/gitops-devsecops-azure.png) +--- + ### version 2.0 - IAM Role sample for CodeBuild & CodePipeline @@ -100,6 +178,8 @@ - Setup `~/.ssh/config` for authorization config ssh key 3rd party repository - Dynamic Tags with COMMIT_HASH +--- + ### version 1.0 - Golang API Rest (bookstore) @@ -109,6 +189,8 @@ - Deploy Kubernetes with Helm Values - Buildspec for AWS CodeBuild & AWS CodePipeline +--- + ### version 0.1 - First deployment GO Apps diff --git a/README.md b/README.md index 85b965b9..e0be07f4 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Kubernetes Deployment for Simple Golang API ![tags](https://img.shields.io/github/v/tag/devopscorner/golang-deployment?sort=semver) [![docker pulls](https://img.shields.io/docker/pulls/devopscorner/bookstore.svg)](https://hub.docker.com/r/devopscorner/bookstore/) ![download all](https://img.shields.io/github/downloads/devopscorner/golang-deployment/total.svg) -![download latest](https://img.shields.io/github/downloads/devopscorner/golang-deployment/3.3/total) +![download latest](https://img.shields.io/github/downloads/devopscorner/golang-deployment/3.4/total) ![view](https://views.whatilearened.today/views/github/devopscorner/golang-deployment.svg) ![clone](https://img.shields.io/badge/dynamic/json?color=success&label=clone&query=count&url=https://github.com/devopscorner/golang-deployment/blob/master/clone.json?raw=True&logo=github) ![issues](https://img.shields.io/github/issues/devopscorner/golang-deployment) @@ -52,6 +52,7 @@ Kubernetes Deployment for Simple Golang API - GitOps & GitOps DevSecOps Flow (Azure DevOps Pipeline), go to [this](docs/gitops-devsecops-flow-azure.md) link - Deployments: - **ArgoCD**, detail [here](docs/deployment-argocd.md) link + - **AWS CloudFormation**, detail [here](docs/deployment-aws-cloudformation.md) link - **AWS Developer Tools** (AWS CodeCommit, AWS CodeBuild & AWS CodePipeline), detail [here](docs/deployment-aws-developer-tools.md) link - **Azure DevOps Pipeline**, detail [here](docs/deployment-azure-devops.md) link - **Bitbucket Pipeline**, detail [here](docs/deployment-bitbucket.md) link @@ -147,4 +148,4 @@ Make sure that you didn't push sensitive information in this repository - Author: **Dwi Fahni Denni (@zeroc0d3)** - Vendor: **DevOps Corner Indonesia (devopscorner.id)** -- License: **Apache v2** +- License: **Apache v2** \ No newline at end of file diff --git a/cloudformation/cicd-cloudformation-aws-codecommit.yaml b/cloudformation/cicd-cloudformation-aws-codecommit.yaml new file mode 100644 index 00000000..96802992 --- /dev/null +++ b/cloudformation/cicd-cloudformation-aws-codecommit.yaml @@ -0,0 +1,313 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: A CloudFormation template for deploying a Golang app using AWS CodePipeline and AWS CodeBuild, sourced from an AWS CodeCommit repository. + +Parameters: + RepositoryName: + Type: String + Description: The name of the AWS CodeCommit repository. + BranchName: + Type: String + Default: master + Description: The branch to use for the source code. + ECRRepoName: + Type: String + Description: The name of the Elastic Container Registry (ECR) repository to use for storing the Docker image. + S3Bucket: + Type: String + Description: The name of the S3 bucket to use for storing pipeline artifacts. + KMSKeyArn: + Type: String + Description: The ARN of the KMS key to use for encrypting pipeline artifacts. + StagingClusterName: + Type: String + Description: The name of the ECS cluster to use for the staging environment. + StagingServiceName: + Type: String + Description: The name of the ECS service to use for the staging environment. + ProductionClusterName: + Type: String + Description: The name of the ECS cluster to use for the production environment. + ProductionServiceName: + Type: String + Description: The name of the ECS service to use for the production environment. + +Resources: + ECRRepo: + Type: AWS::ECR::Repository + Properties: + RepositoryName: !Ref ECRRepoName + + SNSTopic: + Type: AWS::SNS::Topic + Properties: + DisplayName: !Sub "${AWS::StackName}-SNSTopic" + TopicName: !Sub "${AWS::StackName}-SNSTopic" + + SNSTopicSubscription: + Type: AWS::SNS::Subscription + Properties: + Protocol: email + TopicArn: !Ref SNSTopic + Endpoint: support@devopscorner.id + + CodeCommitRepo: + Type: AWS::CodeCommit::Repository + Properties: + RepositoryName: !Ref RepositoryName + + CodeBuildProject: + Type: AWS::CodeBuild::Project + Properties: + Name: !Sub "${AWS::StackName}-CodeBuildProject" + Description: My Golang app + ServiceRole: !GetAtt CodeBuildServiceRole.Arn + TimeoutInMinutes: 60 + Artifacts: + Type: NO_ARTIFACTS + Environment: + ComputeType: BUILD_GENERAL1_SMALL + ## Image: aws/codebuild/standard:5.0 + Image: devopscorner/cicd:codebuild-4.0 + Type: LINUX_CONTAINER + EnvironmentVariables: + - Name: AWS_REGION + Value: ap-southeast-1 + - Name: ENVIRONMENT + Value: !If [IsStaging, "staging", "production"] + - Name: ECR_REPOSITORY_URI + Value: !Join ['', [!GetAtt ECRRepo.RepositoryUri, ':', !Ref ImageTag]] + Source: + Type: CODECOMMIT + Location: !Sub "https://git-codecommit.${AWS::Region}.amazonaws.com/v1/repos/${RepositoryName}" + GitCloneDepth: 1 + ReportBuildStatus: true + # BuildSpec: | + # version: 0.2 + + # phases: + # install: + # commands: + # - echo "Install phase" + # build: + # commands: + # - echo "Build phase" + # - export IMAGE_TAG=$CODEBUILD_RESOLVED_SOURCE_VERSION + # - echo $IMAGE_TAG > image_tag.txt + # - echo "IMAGE_TAG=$IMAGE_TAG" >> build.env + # finally: + # - echo "Pushing Docker image to ECR..." + # - $(aws ecr get-login --no-include-email --region ap-southeast-1) + # - docker push !Join ['', [!GetAtt ECRRepo.RepositoryUri, ':', !Ref Image + # Tag]] + artifacts: + files: + - image_tag.txt + name: build-artifact + + CodePipeline: + Type: AWS::CodePipeline::Pipeline + Properties: + Name: !Sub "${AWS::StackName}-CodePipeline" + RoleArn: !GetAtt CodePipelineServiceRole.Arn + ArtifactStore: + Type: S3 + Location: !Ref S3Bucket + EncryptionKey: + Type: KMS + Id: !Ref KMSKeyArn + Stages: + - Name: Source + Actions: + - Name: Source + ActionTypeId: + Category: Source + Owner: AWS + Provider: CodeCommit + Version: '1' + OutputArtifacts: + - Name: SourceArtifact + Configuration: + RepositoryName: !Ref RepositoryName + BranchName: !Ref BranchName + RunOrder: 1 + + - Name: Build + Actions: + - Name: Build + ActionTypeId: + Category: Build + Owner: AWS + Provider: CodeBuild + Version: '1' + InputArtifacts: + - Name: SourceArtifact + OutputArtifacts: + - Name: BuildArtifact + Configuration: + ProjectName: !Ref CodeBuildProject + EnvironmentVariables: + - Name: IMAGE_TAG + Value: !Ref ImageTag + RunOrder: 2 + + - Name: ManualApproval + Actions: + - Name: ManualApproval + Category: Approval + Owner: AWS + Provider: Manual + Version: '1' + InputArtifacts: + - Name: BuildArtifact + OutputArtifacts: + - Name: ApproveArtifact + Configuration: + NotificationArn: !Ref SNSTopic + CustomData: "Do you want to deploy the latest changes to production?" + RunOrder: 3 + + - Name: DeployStaging + Actions: + - Name: DeployStaging + ActionTypeId: + Category: Deploy + Owner: AWS + Provider: ECS + Version: '1' + InputArtifacts: + - Name: BuildArtifact + - Name: ApproveArtifact + Configuration: + ClusterName: !Ref StagingClusterName + ServiceName: !Ref StagingServiceName + FileName: imagedefinitions.json + ImageUri: !Join ['', [!GetAtt ECRRepo.RepositoryUri, ':', !Ref ImageTag]] + RunOrder: 4 + Condition: + StringEquals: + - !Ref IsStaging + - "true" + + - Name: DeployProd + Actions: + - Name: DeployProd + ActionTypeId: + Category: Deploy + Owner: AWS + Provider: ECS + Version: '1' + InputArtifacts: + - Name: BuildArtifact + - Name: ApproveArtifact + Configuration: + ClusterName: !Ref ProductionClusterName + ServiceName: !Ref ProductionServiceName + FileName: imagedefinitions.json + ImageUri: !Join ['', [!GetAtt ECRRepo.RepositoryUri, ':', !Ref ImageTag]] + RunOrder: 4 + Condition: + StringEquals: + - !Ref IsStaging + - "false" + StartsWith: + ApprovalStatus: "Approved" + + CodePipelineServiceRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: codepipeline.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/AWSCodePipelineFullAccess + Policies: + - PolicyName: AllowECRWriteAccess + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - ecr:BatchCheckLayerAvailability + - ecr:GetDownloadUrlForLayer + - ecr:BatchGetImage + - ecr:PutImage + Resource: !GetAtt ECRRepo.Arn + + CodeBuildServiceRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: codebuild.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/AWSCodeBuildAdminAccess + - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser + - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess + Policies: + - PolicyName: AllowECRReadAccess + + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - ecr:GetAuthorizationToken + - ecr:GetDownloadUrlForLayer + - ecr:BatchGetImage + Resource: '*' + + S3ArtifactStoreBucket: + Type: AWS::S3::Bucket + Properties: + BucketName: !Ref S3Bucket + + S3Object: + Type: AWS::S3::Object + Properties: + Bucket: !Ref S3Bucket + Key: !Sub "${AWS::StackName}/buildspec.yml" + ContentType: text/x-yaml + Body: |- + version: 0.2 + phases: + install: + commands: + - echo "Install phase" + build: + commands: + - echo "Build phase" + - export IMAGE_TAG=$CODEBUILD_RESOLVED_SOURCE_VERSION + - echo $IMAGE_TAG > image_tag.txt + - echo "IMAGE_TAG=$IMAGE_TAG" >> build.env + post_build: + commands: + - echo "Post build phase" + - echo "Pushing Docker image to ECR..." + - $(aws ecr get-login --no-include-email --region ap-southeast-1) + - docker push !Join ['', [!GetAtt ECRRepo.RepositoryUri, ':', !Ref ImageTag]] + - echo "Pushed Docker image to ECR successfully." + +Outputs: + CodePipelineUrl: + Description: The URL of the AWS CodePipeline. + Value: !Sub "https://${AWS::Region}.console.aws.amazon.com/codesuite/codepipeline/pipelines/${AWS::StackName}-CodePipeline/view?region=${AWS::Region}" + ECRRepositoryUrl: + Description: URL of the Elastic Container Registry (ECR) repository. + Value: !GetAtt ECRRepo.RepositoryUri + SNSTopicArn: + Description: ARN of the SNS topic. + Value: !Ref SNSTopic + PipelineName: + Description: Name of the CodePipeline. + Value: !Ref CodePipeline + CodeBuildProjectName: + Description: Name of the CodeBuild project. + Value: !Ref CodeBuildProject diff --git a/cloudformation/cicd-cloudformation-github.yaml b/cloudformation/cicd-cloudformation-github.yaml new file mode 100644 index 00000000..47a1f86e --- /dev/null +++ b/cloudformation/cicd-cloudformation-github.yaml @@ -0,0 +1,297 @@ +AWSTemplateFormatVersion: 2010-09-09 +Description: A CloudFormation template for deploying a Golang app using AWS CodePipeline and AWS CodeBuild, sourced from GitHub. + +Parameters: + GitHubOwner: + Type: String + Description: The GitHub user or organization that owns the repository. + GitHubRepoName: + Type: String + Description: The name of the GitHub repository. + GitHubBranch: + Type: String + Description: The name of the branch to use as the source for the pipeline. + ECRRepoName: + Type: String + Description: The name of the Elastic Container Registry (ECR) repository to use for storing the Docker image. + S3Bucket: + Type: String + Description: The name of the S3 bucket to use for storing pipeline artifacts. + KMSKeyArn: + Type: String + Description: The ARN of the KMS key to use for encrypting pipeline artifacts. + StagingClusterName: + Type: String + Description: The name of the ECS cluster to use for the staging environment. + StagingServiceName: + Type: String + Description: The name of the ECS service to use for the staging environment. + ProductionClusterName: + Type: String + Description: The name of the ECS cluster to use for the production environment. + ProductionServiceName: + Type: String + Description: The name of the ECS service to use for the production environment. + +Resources: + ECRRepo: + Type: AWS::ECR::Repository + Properties: + RepositoryName: !Ref ECRRepoName + + SNSTopic: + Type: AWS::SNS::Topic + Properties: + DisplayName: !Sub "${AWS::StackName}-SNSTopic" + TopicName: !Sub "${AWS::StackName}-SNSTopic" + + SNSTopicSubscription: + Type: AWS::SNS::Subscription + Properties: + Protocol: email + TopicArn: !Ref SNSTopic + Endpoint: support@devopscorner.id + + CodeBuildProject: + Type: AWS::CodeBuild::Project + Properties: + Name: !Sub "${AWS::StackName}-CodeBuildProject" + Description: My Golang app + ServiceRole: !GetAtt CodeBuildServiceRole.Arn + TimeoutInMinutes: 60 + Artifacts: + Type: NO_ARTIFACTS + Environment: + ComputeType: BUILD_GENERAL1_SMALL + ## Image: aws/codebuild/standard:5.0 + Image: devopscorner/cicd:codebuild-4.0 + Type: LINUX_CONTAINER + EnvironmentVariables: + - Name: AWS_REGION + Value: your-aws-region + - Name: ENVIRONMENT + Value: !If [IsStaging, "staging", "production"] + - Name: ECR_REPOSITORY_URI + Value: !Join ['', [!GetAtt ECRRepo.RepositoryUri, ':', !Ref ImageTag]] + Source: + Type: GITHUB + Location: !Sub "https://github.com/devopscorner/golang-deployment" + GitCloneDepth: 1 + BuildSpec: !Sub "${S3ObjectUrl}" + GitCloneDepth: 1 + ReportBuildStatus: true + SourceVersion: !Ref GitHubBranch + artifacts: + files: + - image_tag.txt + name: build-artifact + + CodePipeline: + Type: AWS::CodePipeline::Pipeline + Properties: + Name: !Sub "${AWS::StackName}-CodePipeline" + RoleArn: !GetAtt CodePipelineServiceRole.Arn + ArtifactStore: + Type: S3 + Location: !Ref S3Bucket + EncryptionKey: + Type: KMS + Id: !Ref KMSKeyArn + Stages: + - Name: Source + Actions: + - Name: Source + ActionTypeId: + Category: Source + Owner: ThirdParty + Provider: GitHub + Version: '1' + OutputArtifacts: + - Name: SourceArtifact + Configuration: + Owner: !Ref GitHubOwner + Repo: !Ref GitHubRepoName + Branch: !Ref GitHubBranch + OAuthToken: !Ref GitHubToken + RunOrder: 1 + + - Name: Build + Actions: + - Name: Build + ActionTypeId: + Category: Build + Owner: AWS + Provider: CodeBuild + Version: '1' + InputArtifacts: + - Name: SourceArtifact + OutputArtifacts: + - Name: BuildArtifact + Configuration: + ProjectName: !Ref CodeBuildProject + EnvironmentVariables: + - Name: IMAGE_TAG + Value: !Ref ImageTag + RunOrder: 2 + + - Name: ManualApproval + Actions: + - Name: ManualApproval + Category: Approval + Owner: AWS + Provider: Manual + Version: '1' + InputArtifacts: + - Name: BuildArtifact + OutputArtifacts: + - Name: ApproveArtifact + Configuration: + NotificationArn: !Ref SNSTopic + CustomData: "Do you want to deploy the latest changes to production?" + RunOrder: 3 + + - Name: DeployStaging + Actions: + - Name: DeployStaging + ActionTypeId: + Category: Deploy + Owner: AWS + Provider: ECS + Version: '1' + InputArtifacts: + - Name: BuildArtifact + - Name: ApproveArtifact + Configuration: + ClusterName: !Ref StagingClusterName + ServiceName: !Ref StagingServiceName + FileName: imagedefinitions.json + ImageUri: !Join ['', [!GetAtt ECRRepo.RepositoryUri, ':', !Ref ImageTag]] + RunOrder: 4 + Condition: + StringEquals: + - !Ref IsStaging + - "true" + + - Name: DeployProd + Actions: + - Name: DeployProd + ActionTypeId: + Category: Deploy + Owner: AWS + Provider: ECS + Version: '1' + InputArtifacts: + - Name: BuildArtifact + - Name: ApproveArtifact + Configuration: + ClusterName: !Ref ProductionClusterName + ServiceName: !Ref ProductionServiceName + FileName: imagedefinitions.json + ImageUri: !Join ['', [!GetAtt ECRRepo.RepositoryUri, ':', !Ref ImageTag]] + RunOrder: 4 + Condition: + StringEquals: + - !Ref IsStaging + - "false" + StartsWith: + ApprovalStatus: "Approved" + + CodePipelineServiceRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: codepipeline.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/AWSCodePipelineFullAccess + Policies: + - PolicyName: AllowECRWriteAccess + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - ecr:BatchCheckLayerAvailability + - ecr:GetDownloadUrlForLayer + - ecr:BatchGetImage + - ecr:PutImage + Resource: !GetAtt ECRRepo.Arn + + CodeBuildServiceRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: codebuild.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/AWSCodeBuildAdminAccess + - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser + - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess + Policies: + - PolicyName: AllowECRReadAccess + + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - ecr:GetAuthorizationToken + - ecr:GetDownloadUrlForLayer + - ecr:BatchGetImage + Resource: '*' + + S3ArtifactStoreBucket: + Type: AWS::S3::Bucket + Properties: + BucketName: !Ref S3Bucket + + S3Object: + Type: AWS::S3::Object + Properties: + Bucket: !Ref S3Bucket + Key: !Sub "${AWS::StackName}/buildspec.yml" + ContentType: text/x-yaml + Body: |- + version: 0.2 + phases: + install: + commands: + - echo "Install phase" + build: + commands: + - echo "Build phase" + - export IMAGE_TAG=$CODEBUILD_RESOLVED_SOURCE_VERSION + - echo $IMAGE_TAG > image_tag.txt + - echo "IMAGE_TAG=$IMAGE_TAG" >> build.env + post_build: + commands: + - echo "Post build phase" + - echo "Pushing Docker image to ECR..." + - $(aws ecr get-login --no-include-email --region your-aws-region) + - docker push !Join ['', [!GetAtt ECRRepo.RepositoryUri, ':', !Ref ImageTag]] + - echo "Pushed Docker image to ECR successfully." + +Outputs: + CodePipelineUrl: + Description: The URL of the AWS CodePipeline. + Value: !Sub "https://${AWS::Region}.console.aws.amazon.com/codesuite/codepipeline/pipelines/${AWS::StackName}-CodePipeline/view?region=${AWS::Region}" + ECRRepositoryUrl: + Description: URL of the Elastic Container Registry (ECR) repository. + Value: !GetAtt ECRRepo.RepositoryUri + SNSTopicArn: + Description: ARN of the SNS topic. + Value: !Ref SNSTopic + PipelineName: + Description: Name of the CodePipeline. + Value: !Ref CodePipeline + CodeBuildProjectName: + Description: Name of the CodeBuild project. + Value: !Ref CodeBuildProject diff --git a/docs/deployment-aws-cloudformation.md b/docs/deployment-aws-cloudformation.md new file mode 100644 index 00000000..d1e6de4e --- /dev/null +++ b/docs/deployment-aws-cloudformation.md @@ -0,0 +1,503 @@ +# Golang Deployment - CI/CD with AWS CloudFormation + +Kubernetes Deployment for Simple Golang API + +![goreport](https://goreportcard.com/badge/github.com/devopscorner/golang-deployment) +![all contributors](https://img.shields.io/github/contributors/devopscorner/golang-deployment) +![tags](https://img.shields.io/github/v/tag/devopscorner/golang-deployment?sort=semver) +[![docker pulls](https://img.shields.io/docker/pulls/devopscorner/bookstore.svg)](https://hub.docker.com/r/devopscorner/bookstore/) +![download all](https://img.shields.io/github/downloads/devopscorner/golang-deployment/total.svg) +![view](https://views.whatilearened.today/views/github/devopscorner/golang-deployment.svg) +![clone](https://img.shields.io/badge/dynamic/json?color=success&label=clone&query=count&url=https://github.com/devopscorner/golang-deployment/blob/master/clone.json?raw=True&logo=github) +![issues](https://img.shields.io/github/issues/devopscorner/golang-deployment) +![pull requests](https://img.shields.io/github/issues-pr/devopscorner/golang-deployment) +![forks](https://img.shields.io/github/forks/devopscorner/golang-deployment) +![stars](https://img.shields.io/github/stars/devopscorner/golang-deployment) +[![license](https://img.shields.io/github/license/devopscorner/golang-deployment)](https://img.shields.io/github/license/devopscorner/golang-deployment) + +--- + +## Buildspec Build + +``` +version: 0.2 + +env: + # ==================== # + # Ref: SECRET CONFIG # + # ==================== # + parameter-store: + BUILDNUMBER: /devopscorner/cicd/staging/repo/bookstore/buildnumber + STORE_AWS_ACCOUNT: /devopscorner/cicd/staging/credentials/aws_account + STORE_AWS_ACCESS_KEY: /devopscorner/cicd/staging/credentials/aws_access_key + STORE_AWS_SECRET_KEY: /devopscorner/cicd/staging/credentials/aws_secret_key + STORE_REPO_URL: /devopscorner/cicd/staging/repo/bookstore/url + STORE_REPO_BRANCH: /devopscorner/cicd/staging/repo/bookstore/branch + STORE_REPO_FOLDER: /devopscorner/cicd/staging/repo/bookstore/folder + STORE_EKS_CLUSTER: /devopscorner/cicd/staging/eks_cluster + STORE_BASE64_PUB_KEY: /devopscorner/cicd/staging/credentials/base64_pub_key + STORE_BASE64_PRIV_KEY: /devopscorner/cicd/staging/credentials/base64_priv_key + STORE_BASE64_PEM_KEY: /devopscorner/cicd/staging/credentials/base64_pem_key + STORE_BASE64_SSH_CONFIG: /devopscorner/cicd/staging/credentials/base64_ssh_config + STORE_BASE64_KNOWN_HOSTS: /devopscorner/cicd/staging/credentials/known_hosts + STORE_BASE64_KUBECONFIG: /devopscorner/cicd/staging/credentials/base64_kube_config + + # ===================================== # + # Ref: Pipeline Environment Variables # + # ===================================== # + variables: + ENV_CICD: "dev" + AWS_DEFAULT_REGION: "ap-southeast-1" + INFRA_CICD: "terraform/environment/providers/aws/infra/resources" + INFRA_CICD_PATH: "bookstore" + INFRA_ECR_PATH: "devopscorner/bookstore" + +phases: + pre_build: + commands: + # ======================= # + # Setup Auth Repository # + # ======================= # + - mkdir -p ~/.ssh + - echo "${STORE_BASE64_PUB_KEY}" | base64 -d > ~/.ssh/id_rsa.pub + - echo "${STORE_BASE64_PRIV_KEY}" | base64 -d > ~/.ssh/id_rsa + - echo "${STORE_BASE64_KNOWN_HOSTS}" | base64 -d > ~/.ssh/known_hosts + - chmod 400 ~/.ssh/id_rsa* + - chmod 644 ~/.ssh/known_hosts + - eval "$(ssh-agent -s)" + - ssh-add ~/.ssh/id_rsa + - echo '- DONE -' + build: + commands: + # ========================= # + # Refactoring AWS Account # + # ========================= # + - cd ${CODEBUILD_SRC_DIR} && find ./ -type f -exec sed -i "s/YOUR_AWS_ACCOUNT/${STORE_AWS_ACCOUNT}/g" {} \; + # ============= # + # Build Image # + # ============= # + - make ecr-build-alpine ARGS=${STORE_AWS_ACCOUNT} CI_PATH=${INFRA_ECR_PATH} + # ============== # + # Unit Testing # + # ============== # + # - make unit-test + # ============ # + # Tags Image # + # ============ # + - make ecr-tag-alpine ARGS=${STORE_AWS_ACCOUNT} CI_PATH=${INFRA_ECR_PATH} + - docker images --format "{{.Repository}}:{{.Tag}}" | grep ${INFRA_ECR_PATH} + # ============ # + # Push Image # + # ============ # + - make ecr-push-alpine ARGS=${STORE_AWS_ACCOUNT} TAGS=${INFRA_ECR_PATH} + +artifacts: + files: + - _infra/* + - .aws/* + - docs/* + - src/* + - dockerhub-build.sh + - dockerhub-push.sh + - dockerhub-tag.sh + - ecr-build.sh + - ecr-push.sh + - ecr-tag.sh + - Makefile + name: "artifact-$(date '+%Y%m%d-%H%M%S')" +``` + +## Buildspec Deploy + +``` +version: 0.2 + +env: + # ==================== # + # Ref: SECRET CONFIG # + # ==================== # + parameter-store: + BUILDNUMBER: /devopscorner/cicd/staging/repo/bookstore/buildnumber + STORE_AWS_ACCOUNT: /devopscorner/cicd/staging/credentials/aws_account + STORE_AWS_ACCESS_KEY: /devopscorner/cicd/staging/credentials/aws_access_key + STORE_AWS_SECRET_KEY: /devopscorner/cicd/staging/credentials/aws_secret_key + STORE_REPO_URL: /devopscorner/cicd/staging/repo/bookstore/url + STORE_REPO_BRANCH: /devopscorner/cicd/staging/repo/bookstore/branch + STORE_REPO_FOLDER: /devopscorner/cicd/staging/repo/bookstore/folder + STORE_EKS_CLUSTER: /devopscorner/cicd/staging/eks_cluster + STORE_BASE64_PUB_KEY: /devopscorner/cicd/staging/credentials/base64_pub_key + STORE_BASE64_PRIV_KEY: /devopscorner/cicd/staging/credentials/base64_priv_key + STORE_BASE64_PEM_KEY: /devopscorner/cicd/staging/credentials/base64_pem_key + STORE_BASE64_SSH_CONFIG: /devopscorner/cicd/staging/credentials/base64_ssh_config + STORE_BASE64_KNOWN_HOSTS: /devopscorner/cicd/staging/credentials/known_hosts + STORE_BASE64_KUBECONFIG: /devopscorner/cicd/staging/credentials/base64_kube_config + + # ===================================== # + # Ref: Pipeline Environment Variables # + # ===================================== # + variables: + ENV_CICD: "dev" + AWS_DEFAULT_REGION: "ap-southeast-1" + INFRA_CICD: "terraform/environment/providers/aws/infra/resources" + INFRA_CICD_PATH: "bookstore" + INFRA_ECR_PATH: "devopscorner/bookstore" + +phases: + pre_build: + commands: + # ======================= # + # Setup Auth Repository # + # ======================= # + - mkdir -p ~/.ssh + - mkdir -p ~/.kube + - echo "${STORE_BASE64_PUB_KEY}" | base64 -d > ~/.ssh/id_rsa.pub + - echo "${STORE_BASE64_PRIV_KEY}" | base64 -d > ~/.ssh/id_rsa + - echo "${STORE_BASE64_KNOWN_HOSTS}" | base64 -d > ~/.ssh/known_hosts + - echo "${STORE_BASE64_KUBECONFIG}" | base64 -d > ~/.kube/config + - chmod 400 ~/.ssh/id_rsa* + - chmod 400 ~/.kube/config* + - chmod 644 ~/.ssh/known_hosts + - eval "$(ssh-agent -s)" + - ssh-add ~/.ssh/id_rsa + - echo '- DONE -' + build: + commands: + # ========================= # + # Refactoring AWS Account # + # ========================= # + - cd ${CODEBUILD_SRC_DIR} && find ./ -type f -exec sed -i "s/YOUR_AWS_ACCOUNT/${STORE_AWS_ACCOUNT}/g" {} \; + # ================== # + # Helm Repo Update # + # ================== # + - AWS_REGION=${AWS_DEFAULT_REGION} helm repo add devopscorner-staging s3://devopscorner-helm-chart/staging + - AWS_REGION=${AWS_DEFAULT_REGION} helm repo add devopscorner-prod s3://devopscorner-helm-chart/prod + - helm repo update + # ============ # + # Deploy K8S # + # ============ # + - cd _infra/${ENV_CICD} + - aws eks update-kubeconfig --region ${AWS_DEFAULT_REGION} --name ${STORE_EKS_CLUSTER} + - kubectl version + - kubectl config use-context arn:aws:eks:${AWS_DEFAULT_REGION}:${STORE_AWS_ACCOUNT}:cluster/${STORE_EKS_CLUSTER} + - kubectl get ns -A + - helmfile --version + - helmfile -f helm-template.yml apply + - echo '-- ALL DONE --' + +artifacts: + files: + - _infra/* + - .aws/* + - docs/* + - src/* + - dockerhub-build.sh + - dockerhub-push.sh + - dockerhub-tag.sh + - ecr-build.sh + - ecr-push.sh + - ecr-tag.sh + - Makefile + name: "artifact-$(date '+%Y%m%d-%H%M%S')" +``` + +## Example CI/CD Script `cicd-aws-cloudformation.yaml` + +``` +AWSTemplateFormatVersion: 2010-09-09 +Description: A CloudFormation template for deploying a Golang app using AWS CodePipeline and AWS CodeBuild, sourced from GitHub. + +Parameters: + GitHubOwner: + Type: String + Description: The GitHub user or organization that owns the repository. + GitHubRepoName: + Type: String + Description: The name of the GitHub repository. + GitHubBranch: + Type: String + Description: The name of the branch to use as the source for the pipeline. + ECRRepoName: + Type: String + Description: The name of the Elastic Container Registry (ECR) repository to use for storing the Docker image. + S3Bucket: + Type: String + Description: The name of the S3 bucket to use for storing pipeline artifacts. + KMSKeyArn: + Type: String + Description: The ARN of the KMS key to use for encrypting pipeline artifacts. + StagingClusterName: + Type: String + Description: The name of the ECS cluster to use for the staging environment. + StagingServiceName: + Type: String + Description: The name of the ECS service to use for the staging environment. + ProductionClusterName: + Type: String + Description: The name of the ECS cluster to use for the production environment. + ProductionServiceName: + Type: String + Description: The name of the ECS service to use for the production environment. + +Resources: + ECRRepo: + Type: AWS::ECR::Repository + Properties: + RepositoryName: !Ref ECRRepoName + + SNSTopic: + Type: AWS::SNS::Topic + Properties: + DisplayName: !Sub "${AWS::StackName}-SNSTopic" + TopicName: !Sub "${AWS::StackName}-SNSTopic" + + SNSTopicSubscription: + Type: AWS::SNS::Subscription + Properties: + Protocol: email + TopicArn: !Ref SNSTopic + Endpoint: support@devopscorner.id + + CodeBuildProject: + Type: AWS::CodeBuild::Project + Properties: + Name: !Sub "${AWS::StackName}-CodeBuildProject" + Description: My Golang app + ServiceRole: !GetAtt CodeBuildServiceRole.Arn + TimeoutInMinutes: 60 + Artifacts: + Type: NO_ARTIFACTS + Environment: + ComputeType: BUILD_GENERAL1_SMALL + ## Image: aws/codebuild/standard:5.0 + Image: devopscorner/cicd:codebuild-4.0 + Type: LINUX_CONTAINER + EnvironmentVariables: + - Name: AWS_REGION + Value: your-aws-region + - Name: ENVIRONMENT + Value: !If [IsStaging, "staging", "production"] + - Name: ECR_REPOSITORY_URI + Value: !Join ['', [!GetAtt ECRRepo.RepositoryUri, ':', !Ref ImageTag]] + Source: + Type: GITHUB + Location: !Sub "https://github.com/devopscorner/golang-deployment" + GitCloneDepth: 1 + BuildSpec: !Sub "${S3ObjectUrl}" + GitCloneDepth: 1 + ReportBuildStatus: true + SourceVersion: !Ref GitHubBranch + artifacts: + files: + - image_tag.txt + name: build-artifact + + CodePipeline: + Type: AWS::CodePipeline::Pipeline + Properties: + Name: !Sub "${AWS::StackName}-CodePipeline" + RoleArn: !GetAtt CodePipelineServiceRole.Arn + ArtifactStore: + Type: S3 + Location: !Ref S3Bucket + EncryptionKey: + Type: KMS + Id: !Ref KMSKeyArn + Stages: + - Name: Source + Actions: + - Name: Source + ActionTypeId: + Category: Source + Owner: ThirdParty + Provider: GitHub + Version: '1' + OutputArtifacts: + - Name: SourceArtifact + Configuration: + Owner: !Ref GitHubOwner + Repo: !Ref GitHubRepoName + Branch: !Ref GitHubBranch + OAuthToken: !Ref GitHubToken + RunOrder: 1 + + - Name: Build + Actions: + - Name: Build + ActionTypeId: + Category: Build + Owner: AWS + Provider: CodeBuild + Version: '1' + InputArtifacts: + - Name: SourceArtifact + OutputArtifacts: + - Name: BuildArtifact + Configuration: + ProjectName: !Ref CodeBuildProject + EnvironmentVariables: + - Name: IMAGE_TAG + Value: !Ref ImageTag + RunOrder: 2 + + - Name: ManualApproval + Actions: + - Name: ManualApproval + Category: Approval + Owner: AWS + Provider: Manual + Version: '1' + InputArtifacts: + - Name: BuildArtifact + OutputArtifacts: + - Name: ApproveArtifact + Configuration: + NotificationArn: !Ref SNSTopic + CustomData: "Do you want to deploy the latest changes to production?" + RunOrder: 3 + + - Name: DeployStaging + Actions: + - Name: DeployStaging + ActionTypeId: + Category: Deploy + Owner: AWS + Provider: ECS + Version: '1' + InputArtifacts: + - Name: BuildArtifact + - Name: ApproveArtifact + Configuration: + ClusterName: !Ref StagingClusterName + ServiceName: !Ref StagingServiceName + FileName: imagedefinitions.json + ImageUri: !Join ['', [!GetAtt ECRRepo.RepositoryUri, ':', !Ref ImageTag]] + RunOrder: 4 + Condition: + StringEquals: + - !Ref IsStaging + - "true" + + - Name: DeployProd + Actions: + - Name: DeployProd + ActionTypeId: + Category: Deploy + Owner: AWS + Provider: ECS + Version: '1' + InputArtifacts: + - Name: BuildArtifact + - Name: ApproveArtifact + Configuration: + ClusterName: !Ref ProductionClusterName + ServiceName: !Ref ProductionServiceName + FileName: imagedefinitions.json + ImageUri: !Join ['', [!GetAtt ECRRepo.RepositoryUri, ':', !Ref ImageTag]] + RunOrder: 4 + Condition: + StringEquals: + - !Ref IsStaging + - "false" + StartsWith: + ApprovalStatus: "Approved" + + CodePipelineServiceRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: codepipeline.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/AWSCodePipelineFullAccess + Policies: + - PolicyName: AllowECRWriteAccess + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - ecr:BatchCheckLayerAvailability + - ecr:GetDownloadUrlForLayer + - ecr:BatchGetImage + - ecr:PutImage + Resource: !GetAtt ECRRepo.Arn + + CodeBuildServiceRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: codebuild.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/AWSCodeBuildAdminAccess + - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser + - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess + Policies: + - PolicyName: AllowECRReadAccess + + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - ecr:GetAuthorizationToken + - ecr:GetDownloadUrlForLayer + - ecr:BatchGetImage + Resource: '*' + + S3ArtifactStoreBucket: + Type: AWS::S3::Bucket + Properties: + BucketName: !Ref S3Bucket + + S3Object: + Type: AWS::S3::Object + Properties: + Bucket: !Ref S3Bucket + Key: !Sub "${AWS::StackName}/buildspec.yml" + ContentType: text/x-yaml + Body: |- + version: 0.2 + phases: + install: + commands: + - echo "Install phase" + build: + commands: + - echo "Build phase" + - export IMAGE_TAG=$CODEBUILD_RESOLVED_SOURCE_VERSION + - echo $IMAGE_TAG > image_tag.txt + - echo "IMAGE_TAG=$IMAGE_TAG" >> build.env + post_build: + commands: + - echo "Post build phase" + - echo "Pushing Docker image to ECR..." + - $(aws ecr get-login --no-include-email --region your-aws-region) + - docker push !Join ['', [!GetAtt ECRRepo.RepositoryUri, ':', !Ref ImageTag]] + - echo "Pushed Docker image to ECR successfully." + +Outputs: + CodePipelineUrl: + Description: The URL of the AWS CodePipeline. + Value: !Sub "https://${AWS::Region}.console.aws.amazon.com/codesuite/codepipeline/pipelines/${AWS::StackName}-CodePipeline/view?region=${AWS::Region}" + ECRRepositoryUrl: + Description: URL of the Elastic Container Registry (ECR) repository. + Value: !GetAtt ECRRepo.RepositoryUri + SNSTopicArn: + Description: ARN of the SNS topic. + Value: !Ref SNSTopic + PipelineName: + Description: Name of the CodePipeline. + Value: !Ref CodePipeline + CodeBuildProjectName: + Description: Name of the CodeBuild project. + Value: !Ref CodeBuildProject +``` diff --git a/docs/deployment-terraform.md b/docs/deployment-terraform.md index 7f22f024..2c0b6f71 100644 --- a/docs/deployment-terraform.md +++ b/docs/deployment-terraform.md @@ -28,9 +28,21 @@ Kubernetes Deployment for Simple Golang API - ECS task definition and service creation for staging environment - CloudWatch Event rule and target for manual approval notification - Outputs for the staging and production URLs, build number, and semantic versions for staging and production. - - Using custom image AWS CodeBuild `devopscorner/cicd:4.0` + - Using custom image AWS CodeBuild `devopscorner/cicd:codebuild-4.0` ``` +### Golang Terraform Pipeline (GitHub, AWS CodeBuild, AWS Pipeline, Amazon SNS) ### + +# This script includes: +# - ECR repository creation +# - SNS topic creation and subscription +# - CodeBuild project creation for production and staging environments +# - CodePipeline creation with four stages: Source, Build, ManualApproval, and Deploy (two times) +# - ECS task definition and service creation for staging environment +# - CloudWatch Event rule and target for manual approval notification +# - Outputs for the staging and production URLs, build number, and semantic versions for staging and production. +# - Using custom image AWS CodeBuild `devopscorner/cicd:codebuild-4.0` + terraform { required_version = ">= 1.0.9" @@ -49,7 +61,7 @@ provider "aws" { data "aws_caller_identity" "current" {} resource "aws_ecr_repository" "bookstore" { - name = "bookstore" + name = "devopscorner/bookstore" } resource "aws_sns_topic" "bookstore-topic" { @@ -59,7 +71,7 @@ resource "aws_sns_topic" "bookstore-topic" { resource "aws_sns_topic_subscription" "bookstore-subscription" { topic_arn = aws_sns_topic.bookstore-topic.arn protocol = "email" - endpoint = "youremail@example.com" + endpoint = "support@devopscorner.id" } resource "aws_codebuild_project" "bookstore-prod" { @@ -73,8 +85,8 @@ resource "aws_codebuild_project" "bookstore-prod" { environment { compute_type = "BUILD_GENERAL1_SMALL" # image = "aws/codebuild/standard:5.0" - image = "devopscorner/cicd:4.0" - type = "LINUX_CONTAINER" + image = "devopscorner/cicd:codebuild-4.0" + type = "LINUX_CONTAINER" environment_variable { name = "AWS_REGION" value = "ap-southeast-1" @@ -110,8 +122,8 @@ resource "aws_codebuild_project" "bookstore-staging" { environment { compute_type = "BUILD_GENERAL1_SMALL" # image = "aws/codebuild/standard:5.0" - image = "devopscorner/cicd:4.0" - type = "LINUX_CONTAINER" + image = "devopscorner/cicd:codebuild-4.0" + type = "LINUX_CONTAINER" environment_variable { name = "AWS_REGION" value = "ap-southeast-1" @@ -142,7 +154,6 @@ resource "aws_codepipeline" "bookstore" { artifact_store { type = "S3" location = "your-artifact-store-bucket" - encryption_key { type = "KMS" id = "your-kms-key-id" @@ -209,7 +220,7 @@ resource "aws_codepipeline" "bookstore" { version = "1" input_artifacts = ["BuildArtifact"] configuration = { - ClusterName = "my-ecs-cluster" + ClusterName = "bookstore-cluster" ServiceName = "bookstore-staging" FileName = "imagedefinitions.json" ImageUri = "${aws_ecr_repository.bookstore.repository_url}:${var.semver_staging}" @@ -227,7 +238,7 @@ resource "aws_codepipeline" "bookstore" { version = "1" input_artifacts = ["BuildArtifact"] configuration = { - ClusterName = "my-ecs-cluster" + ClusterName = "bookstore-cluster" ServiceName = "bookstore-prod" FileName = "imagedefinitions.json" ImageUri = "${aws_ecr_repository.bookstore.repository_url}:${var.semver_prod}" @@ -243,22 +254,21 @@ resource "aws_codepipeline" "bookstore" { resource "aws_ecs_task_definition" "bookstore" { family = "bookstore" - container_definitions = jsonencode([ - { - name = "bookstore" - image = "${aws_ecr_repository.bookstore.repository_url}:${var.semver_staging}" - cpu = 512 - memory_reservation = 512 - port_mappings = { - container_port = 8080 - host_port = 0 - } + container_definitions = jsonencode([{ + name = "bookstore" + image = "${aws_ecr_repository.bookstore.repository_url}:${var.semver_staging}" + cpu = 512 + memory_reservation = 512 + port_mappings = { + container_port = 8080 + host_port = 0 + } }]) } resource "aws_ecs_service" "bookstore-staging" { name = "bookstore-staging" - cluster = "my-ecs-cluster" + cluster = "bookstore-cluster" task_definition = aws_ecs_task_definition.bookstore.arn desired_count = 2 launch_type = "EC2" diff --git a/terraform/cicd-terraform-aws-codecommit.tf b/terraform/cicd-terraform-aws-codecommit.tf new file mode 100644 index 00000000..12c2dc5c --- /dev/null +++ b/terraform/cicd-terraform-aws-codecommit.tf @@ -0,0 +1,279 @@ +### Golang Terraform Pipeline (AWS CodeCommit, AWS CodeBuild, AWS Pipeline, Amazon SNS) ### + +# This script includes: +# - ECR repository creation +# - SNS topic creation and subscription +# - CodeBuild project creation for production and staging environments +# - CodePipeline creation with four stages: Source, Build, ManualApproval, and Deploy (two times) +# - ECS task definition and service creation for staging environment +# - CloudWatch Event rule and target for manual approval notification +# - Outputs for the staging and production URLs, build number, and semantic versions for staging and production. +# - Using custom image AWS CodeBuild `devopscorner/cicd:codebuild-4.0` + +terraform { + required_version = ">= 1.0.9" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 3.63.0, < 4.0" + } + } +} + +provider "aws" { + region = "ap-southeast-1" +} + +data "aws_caller_identity" "current" {} + +resource "aws_ecr_repository" "bookstore" { + name = "devopscorner/bookstore" +} + +resource "aws_sns_topic" "bookstore-topic" { + name = "bookstore-topic" +} + +resource "aws_sns_topic_subscription" "bookstore-subscription" { + topic_arn = aws_sns_topic.bookstore-topic.arn + protocol = "email" + endpoint = "support@devopscorner.id" +} + +resource "aws_codebuild_project" "bookstore-prod" { + name = "bookstore-prod" + description = "My Golang app for production" + service_role = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/CodeBuildServiceRole" + build_timeout = "60" + artifacts { + type = "NO_ARTIFACTS" + } + environment { + compute_type = "BUILD_GENERAL1_SMALL" + # image = "aws/codebuild/standard:5.0" + image = "devopscorner/cicd:codebuild-4.0" + type = "LINUX_CONTAINER" + environment_variable { + name = "AWS_REGION" + value = "ap-southeast-1" + } + environment_variable { + name = "ENVIRONMENT" + value = "prod" + } + environment_variable { + name = "ECR_REPOSITORY_URI" + value = aws_ecr_repository.bookstore.repository_url + } + } + source { + type = "CODECOMMIT" + location = "your-repo-name" + git_clone_depth = 1 + buildspec = file("${path.module}/buildspec.yaml") + } +} + +resource "aws_codebuild_project" "bookstore-staging" { + name = "bookstore-staging" + description = "My Golang app for staging" + service_role = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/CodeBuildServiceRole" + build_timeout = "60" + artifacts { + type = "NO_ARTIFACTS" + } + environment { + compute_type = "BUILD_GENERAL1_SMALL" + # image = "aws/codebuild/standard:5.0" + image = "devopscorner/cicd:codebuild-4.0" + type = "LINUX_CONTAINER" + environment_variable { + name = "AWS_REGION" + value = "ap-southeast-1" + } + environment_variable { + name = "ENVIRONMENT" + value = "staging" + } + environment_variable { + name = "ECR_REPOSITORY_URI" + value = aws_ecr_repository.bookstore.repository_url + } + } + source { + type = "CODECOMMIT" + location = "your-repo-name" + git_clone_depth = 1 + buildspec = file("${path.module}/buildspec.yaml") + } +} + +resource "aws_codepipeline" "bookstore" { + name = "bookstore" + role_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/CodePipelineServiceRole" + artifact_store { + type = "S3" + location = "your-artifact-store-bucket" + encryption_key { + type = "KMS" + id = "your-kms-key-id" + } + } + + stage { + name = "Source" + action { + name = "Source" + category = "Source" + owner = "AWS" + provider = "CodeCommit" + version = "1" + output_artifacts = ["SourceArtifact"] + configuration = { + RepositoryName = "your-repo-name" + BranchName = "main" + } + } + } + + stage { + name = "Build" + actions { + name = "Build" + category = "Build" + owner = "AWS" + provider = "CodeBuild" + version = "1" + input_artifacts = ["SourceArtifact"] + output_artifacts = ["BuildArtifact"] + configuration = { + ProjectName = aws_codebuild_project.bookstore-staging.name + } + } + } + + stage { + name = "ManualApproval" + actions { + name = "ManualApproval" + category = "Approval" + owner = "AWS" + provider = "Manual" + version = "1" + input_artifacts = ["BuildArtifact"] + configuration = { + NotificationArn = aws_sns_topic.bookstore-topic.arn + CustomData = "Do you want to deploy the latest changes to production?" + } + } + } + + stage { + name = "Deploy-Staging" + actions { + name = "Deploy-Staging" + category = "Deploy" + owner = "AWS" + provider = "ECS" + version = "1" + input_artifacts = ["BuildArtifact"] + configuration = { + ClusterName = "bookstore-cluster" + ServiceName = "bookstore-staging" + FileName = "imagedefinitions.json" + ImageUri = "${aws_ecr_repository.bookstore.repository_url}:${var.semver_staging}" + } + } + } + + stage { + name = "Deploy-Prod" + actions { + name = "Deploy-Prod" + category = "Deploy" + owner = "AWS" + provider = "ECS" + version = "1" + input_artifacts = ["BuildArtifact"] + configuration = { + ClusterName = "bookstore-cluster" + ServiceName = "bookstore-prod" + FileName = "imagedefinitions.json" + ImageUri = "${aws_ecr_repository.bookstore.repository_url}:${var.semver_prod}" + } + condition { + type = "StartsWith" + variable = "ApprovalStatus" + value = "Approved" + } + } + } +} + +resource "aws_ecs_task_definition" "bookstore" { + family = "bookstore" + container_definitions = jsonencode([{ + name = "bookstore" + image = "${aws_ecr_repository.bookstore.repository_url}:${var.semver_staging}" + cpu = 512 + memory_reservation = 512 + port_mappings = { + container_port = 8080 + host_port = 0 + } + }]) +} + +resource "aws_ecs_service" "bookstore-staging" { + name = "bookstore-staging" + cluster = "bookstore-cluster" + task_definition = aws_ecs_task_definition.bookstore.arn + desired_count = 2 + launch_type = "EC2" + load_balancer { + target_group_arn = "your-target-group-arn" + container_name = "bookstore" + container_port = 8080 + } +} + +resource "aws_cloudwatch_event_rule" "bookstore-manual-approval" { + name = "bookstore-manual-approval" + description = "Manual approval rule for my Golang app" + event_pattern = jsonencode({ + source = ["aws.codepipeline"] + detail_type = ["CodePipeline Action Execution State Change"] + detail = { + pipeline = ["${aws_codepipeline.bookstore.name}"] + stage = ["ManualApproval"] + action = ["ManualApproval"] + state = ["STARTED", "SUCCEEDED", "FAILED"] + } + }) +} + +resource "aws_cloudwatch_event_target" "bookstore-manual-approval" { + rule = aws_cloudwatch_event_rule.bookstore-manual-approval.name + arn = aws_sns_topic.bookstore-topic.arn +} + +output "bookstore-staging-url" { + value = aws_alb.bookstore-staging.dns_name +} + +output "bookstore-prod-url" { + value = aws_alb.bookstore-prod.dns_name +} + +output "bookstore-build-number" { + value = aws_codebuild_project.bookstore-staging.build_number +} + +output "bookstore-semver-staging" { + value = var.semver_staging +} + +output "bookstore-semver-prod" { + value = var.semver_prod +} diff --git a/terraform/cicd-terraform.tf b/terraform/cicd-terraform-github.tf similarity index 91% rename from terraform/cicd-terraform.tf rename to terraform/cicd-terraform-github.tf index 3fe3261b..2ec0d431 100644 --- a/terraform/cicd-terraform.tf +++ b/terraform/cicd-terraform-github.tf @@ -8,7 +8,7 @@ # - ECS task definition and service creation for staging environment # - CloudWatch Event rule and target for manual approval notification # - Outputs for the staging and production URLs, build number, and semantic versions for staging and production. -# - Using custom image AWS CodeBuild `devopscorner/cicd:4.0` +# - Using custom image AWS CodeBuild `devopscorner/cicd:codebuild-4.0` terraform { required_version = ">= 1.0.9" @@ -28,7 +28,7 @@ provider "aws" { data "aws_caller_identity" "current" {} resource "aws_ecr_repository" "bookstore" { - name = "bookstore" + name = "devopscorner/bookstore" } resource "aws_sns_topic" "bookstore-topic" { @@ -38,7 +38,7 @@ resource "aws_sns_topic" "bookstore-topic" { resource "aws_sns_topic_subscription" "bookstore-subscription" { topic_arn = aws_sns_topic.bookstore-topic.arn protocol = "email" - endpoint = "youremail@example.com" + endpoint = "support@devopscorner.id" } resource "aws_codebuild_project" "bookstore-prod" { @@ -52,7 +52,7 @@ resource "aws_codebuild_project" "bookstore-prod" { environment { compute_type = "BUILD_GENERAL1_SMALL" # image = "aws/codebuild/standard:5.0" - image = "devopscorner/cicd:4.0" + image = "devopscorner/cicd:codebuild-4.0" type = "LINUX_CONTAINER" environment_variable { name = "AWS_REGION" @@ -89,7 +89,7 @@ resource "aws_codebuild_project" "bookstore-staging" { environment { compute_type = "BUILD_GENERAL1_SMALL" # image = "aws/codebuild/standard:5.0" - image = "devopscorner/cicd:4.0" + image = "devopscorner/cicd:codebuild-4.0" type = "LINUX_CONTAINER" environment_variable { name = "AWS_REGION" @@ -121,7 +121,6 @@ resource "aws_codepipeline" "bookstore" { artifact_store { type = "S3" location = "your-artifact-store-bucket" - encryption_key { type = "KMS" id = "your-kms-key-id" @@ -188,7 +187,7 @@ resource "aws_codepipeline" "bookstore" { version = "1" input_artifacts = ["BuildArtifact"] configuration = { - ClusterName = "my-ecs-cluster" + ClusterName = "bookstore-cluster" ServiceName = "bookstore-staging" FileName = "imagedefinitions.json" ImageUri = "${aws_ecr_repository.bookstore.repository_url}:${var.semver_staging}" @@ -206,7 +205,7 @@ resource "aws_codepipeline" "bookstore" { version = "1" input_artifacts = ["BuildArtifact"] configuration = { - ClusterName = "my-ecs-cluster" + ClusterName = "bookstore-cluster" ServiceName = "bookstore-prod" FileName = "imagedefinitions.json" ImageUri = "${aws_ecr_repository.bookstore.repository_url}:${var.semver_prod}" @@ -222,22 +221,21 @@ resource "aws_codepipeline" "bookstore" { resource "aws_ecs_task_definition" "bookstore" { family = "bookstore" - container_definitions = jsonencode([ - { - name = "bookstore" - image = "${aws_ecr_repository.bookstore.repository_url}:${var.semver_staging}" - cpu = 512 - memory_reservation = 512 - port_mappings = { - container_port = 8080 - host_port = 0 - } + container_definitions = jsonencode([{ + name = "bookstore" + image = "${aws_ecr_repository.bookstore.repository_url}:${var.semver_staging}" + cpu = 512 + memory_reservation = 512 + port_mappings = { + container_port = 8080 + host_port = 0 + } }]) } resource "aws_ecs_service" "bookstore-staging" { name = "bookstore-staging" - cluster = "my-ecs-cluster" + cluster = "bookstore-cluster" task_definition = aws_ecs_task_definition.bookstore.arn desired_count = 2 launch_type = "EC2" @@ -286,4 +284,4 @@ output "bookstore-semver-staging" { output "bookstore-semver-prod" { value = var.semver_prod -} \ No newline at end of file +}