├── .argocd └── cicd-argocd.yml ├── .aws ├── buildspec-build-nosource.yml ├── buildspec-build.yml ├── buildspec-deploy-nosource.yml ├── buildspec-deploy.yml ├── cicd-aws-codepipeline.yml ├── known_hosts └── ssh-config ├── .azure-devops ├── cicd-azure-devops.yml └── repo-sync-from-azure-devops.yml ├── .bitbucket ├── cicd-bitbucket.yml └── repo-sync-from-bitbucket.yml ├── .circleci └── cicd-circleci.yml ├── .codecatalyst └── workflows │ ├── Workflow_GitHubAction.yaml │ └── taskdef.json ├── .dockerignore ├── .droneci └── cicd-droneci.yml ├── .github └── workflows │ ├── cicd-github.yml │ ├── clone.yml │ └── repo-sync-from-github.yml ├── .gitignore ├── .gitlab ├── cicd-gitlab.yml └── repo-sync-from-gitlab.yml ├── .goreleaser.yml ├── .jenkins ├── cicd-jenkins.jenkinsfile ├── jenkins-ci-aws-codecommit.jenkinsfile ├── jenkins-ci-azure-devops.jenkinsfile ├── jenkins-ci-bitbucket.jenkinsfile ├── jenkins-ci-github.jenkinsfile ├── jenkins-ci-gitlab.jenkinsfile └── jenkins-cicd.jenkinsfile ├── .openshift └── cicd-openshift.yml ├── .semaphoreci └── cicd-semaphoreci.yml ├── .spinnaker └── cicd-spinnaker.yml ├── .travisci └── cicd-travisci.yml ├── CHANGELOG.md ├── Dockerfile ├── Dockerfile.alpine-3.15 ├── Dockerfile.alpine-3.16 ├── Dockerfile.alpine-3.17 ├── LICENSE ├── Makefile ├── README.md ├── _infra ├── helm │ └── dev │ │ ├── api-grpc-values.yml │ │ ├── api-rest-values.yml │ │ └── helm-template.yml └── iam │ ├── ecr-codebuild-permission.json │ ├── policy-cicd.json │ ├── policy-kms-GenerateDataKey.json │ └── policy-kms.json ├── clone.json ├── cloudformation ├── cicd-cloudformation-aws-codecommit.yaml └── cicd-cloudformation-github.yaml ├── docker-compose.yml ├── dockerhub-build.sh ├── dockerhub-push.sh ├── dockerhub-tag.sh ├── docs ├── README.md ├── RESTful API - Golang Bookstore - v2.0.postman_collection.json ├── RESTful API - Golang Bookstore - v2.1.postman_collection.json ├── assets │ ├── gitops-devsecops-azure.png │ └── gitops-flow-azure.png ├── container-bookstore-dockerhub.md ├── container-bookstore-ecr.md ├── deployment-amazon-codecatalyst.md ├── deployment-argocd.md ├── deployment-aws-cloudformation.md ├── deployment-aws-developer-tools.md ├── deployment-azure-devops.md ├── deployment-bitbucket.md ├── deployment-circleci.md ├── deployment-droneci.md ├── deployment-github.md ├── deployment-gitlab.md ├── deployment-jenkins-spinnaker.md ├── deployment-jenkins.md ├── deployment-openshift.md ├── deployment-semaphoreci.md ├── deployment-spinnaker.md ├── deployment-terraform.md ├── deployment-travisci.md ├── gitops-devsecops-flow-azure.md ├── test-grpc-api.md ├── test-restful-api.md └── workflow-cicd-bookstore-pipeline.md ├── ecr-build.sh ├── ecr-pull.sh ├── ecr-push.sh ├── ecr-tag.sh ├── entrypoint.sh ├── git-clone.sh ├── run-docker.sh ├── src ├── .env.example ├── config │ ├── config.go │ ├── config_test.go │ └── const.go ├── controller │ ├── book_controller.go │ ├── book_controller_test.go │ ├── login_controller.go │ └── login_controller_test.go ├── driver │ ├── db.go │ ├── dynamo.go │ ├── mysql.go │ ├── psql.go │ └── sqlite.go ├── go.mod ├── go.sum ├── main.go ├── main_test.go ├── middleware │ ├── auth_middleware.go │ └── auth_middleware_test.go ├── migrate_book.go.example ├── migrate_book_dynamo.go.example ├── model │ └── book.go ├── repository │ └── book_repository.go ├── routes │ └── book_routes.go └── view │ ├── book_view.go │ ├── error_view.go │ └── login_view.go ├── start-build.sh └── terraform ├── cicd-terraform-aws-codecommit.tf └── cicd-terraform-github.tf /.argocd/cicd-argocd.yml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: bookstore 5 | namespace: argocd 6 | spec: 7 | project: default 8 | source: 9 | repoURL: https://github.com/devopscorner/golang-deployment.git 10 | path: manifests 11 | targetRevision: HEAD 12 | destination: 13 | server: https://your-kubernetes-server 14 | namespace: bookstore 15 | syncPolicy: 16 | automated: 17 | prune: true 18 | selfHeal: true 19 | syncOptions: 20 | - Validate=false 21 | healthChecks: 22 | - type: DeploymentRollout 23 | deploymentName: bookstore-deployment 24 | ignoreDifferences: 25 | - kind: Service 26 | jsonPointers: 27 | - /spec/clusterIP 28 | - kind: Deployment 29 | jsonPointers: 30 | - /spec/selector/matchLabels 31 | appVersion: ${VERSION} 32 | -------------------------------------------------------------------------------- /.aws/buildspec-build-nosource.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | # ==================== # 5 | # Ref: SECRET CONFIG # 6 | # ==================== # 7 | parameter-store: 8 | BUILDNUMBER: /devopscorner/cicd/staging/repo/bookstore/buildnumber 9 | STORE_AWS_ACCOUNT: /devopscorner/cicd/staging/credentials/aws_account 10 | STORE_AWS_ACCESS_KEY: /devopscorner/cicd/staging/credentials/aws_access_key 11 | STORE_AWS_SECRET_KEY: /devopscorner/cicd/staging/credentials/aws_secret_key 12 | STORE_REPO_URL: /devopscorner/cicd/staging/repo/bookstore/url 13 | STORE_REPO_BRANCH: /devopscorner/cicd/staging/repo/bookstore/branch 14 | STORE_REPO_FOLDER: /devopscorner/cicd/staging/repo/bookstore/folder 15 | STORE_EKS_CLUSTER: /devopscorner/cicd/staging/eks_cluster 16 | STORE_BASE64_PUB_KEY: /devopscorner/cicd/staging/credentials/base64_pub_key 17 | STORE_BASE64_PRIV_KEY: /devopscorner/cicd/staging/credentials/base64_priv_key 18 | STORE_BASE64_PEM_KEY: /devopscorner/cicd/staging/credentials/base64_pem_key 19 | STORE_BASE64_SSH_CONFIG: /devopscorner/cicd/staging/credentials/base64_ssh_config 20 | STORE_BASE64_KNOWN_HOSTS: /devopscorner/cicd/staging/credentials/known_hosts 21 | STORE_BASE64_KUBECONFIG: /devopscorner/cicd/staging/credentials/base64_kube_config 22 | 23 | # ===================================== # 24 | # Ref: Pipeline Environment Variables # 25 | # ===================================== # 26 | variables: 27 | ENV_CICD: "dev" 28 | AWS_DEFAULT_REGION: "ap-southeast-1" 29 | INFRA_CICD: "terraform/environment/providers/aws/infra/resources" 30 | INFRA_CICD_PATH: "bookstore" 31 | INFRA_ECR_PATH: "devopscorner/bookstore" 32 | 33 | phases: 34 | pre_build: 35 | commands: 36 | # ======================= # 37 | # Setup Auth Repository # 38 | # ======================= # 39 | - mkdir -p ~/.ssh 40 | - echo "${STORE_BASE64_PUB_KEY}" | base64 -d > ~/.ssh/id_rsa.pub 41 | - echo "${STORE_BASE64_PRIV_KEY}" | base64 -d > ~/.ssh/id_rsa 42 | - echo "${STORE_BASE64_KNOWN_HOSTS}" | base64 -d > ~/.ssh/known_hosts 43 | - echo "${STORE_BASE64_SSH_CONFIG}" | base64 -d > ~/.ssh/config 44 | - chmod 400 ~/.ssh/id_rsa* 45 | - chmod 400 ~/.ssh/config* 46 | - chmod 644 ~/.ssh/known_hosts 47 | - eval "$(ssh-agent -s)" 48 | - ssh-add ~/.ssh/id_rsa 49 | # =========================== # 50 | # Using Spesific Repository # 51 | # =========================== # 52 | - GIT_SSH_COMMAND='ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes -F /dev/null' git clone --depth 5 ${STORE_REPO_URL} 53 | - echo '- DONE -' 54 | build: 55 | commands: 56 | # ========================= # 57 | # Refactoring AWS Account # 58 | # ========================= # 59 | - cd ${STORE_REPO_FOLDER} && find ./ -type f -exec sed -i "s/YOUR_AWS_ACCOUNT/${STORE_AWS_ACCOUNT}/g" {} \; 60 | # ============= # 61 | # Build Image # 62 | # ============= # 63 | - make ecr-build-alpine ARGS=${STORE_AWS_ACCOUNT} CI_PATH=${INFRA_ECR_PATH} 64 | # ============== # 65 | # Unit Testing # 66 | # ============== # 67 | # - make unit-test 68 | # ============ # 69 | # Tags Image # 70 | # ============ # 71 | - make ecr-tag-alpine ARGS=${STORE_AWS_ACCOUNT} CI_PATH=${INFRA_ECR_PATH} 72 | - docker images --format "{{.Repository}}:{{.Tag}}" | grep ${INFRA_ECR_PATH} 73 | # ============ # 74 | # Push Image # 75 | # ============ # 76 | - make ecr-push-alpine ARGS=${STORE_AWS_ACCOUNT} TAGS=${INFRA_ECR_PATH} 77 | 78 | artifacts: 79 | files: 80 | - _infra/* 81 | - .aws/* 82 | - docs/* 83 | - src/* 84 | - dockerhub-build.sh 85 | - dockerhub-push.sh 86 | - dockerhub-tag.sh 87 | - ecr-build.sh 88 | - ecr-push.sh 89 | - ecr-tag.sh 90 | - Makefile 91 | name: "artifact-$(date '+%Y%m%d-%H%M%S')" -------------------------------------------------------------------------------- /.aws/buildspec-build.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | # ==================== # 5 | # Ref: SECRET CONFIG # 6 | # ==================== # 7 | parameter-store: 8 | BUILDNUMBER: /devopscorner/cicd/staging/repo/bookstore/buildnumber 9 | STORE_AWS_ACCOUNT: /devopscorner/cicd/staging/credentials/aws_account 10 | STORE_AWS_ACCESS_KEY: /devopscorner/cicd/staging/credentials/aws_access_key 11 | STORE_AWS_SECRET_KEY: /devopscorner/cicd/staging/credentials/aws_secret_key 12 | STORE_REPO_URL: /devopscorner/cicd/staging/repo/bookstore/url 13 | STORE_REPO_BRANCH: /devopscorner/cicd/staging/repo/bookstore/branch 14 | STORE_REPO_FOLDER: /devopscorner/cicd/staging/repo/bookstore/folder 15 | STORE_EKS_CLUSTER: /devopscorner/cicd/staging/eks_cluster 16 | STORE_BASE64_PUB_KEY: /devopscorner/cicd/staging/credentials/base64_pub_key 17 | STORE_BASE64_PRIV_KEY: /devopscorner/cicd/staging/credentials/base64_priv_key 18 | STORE_BASE64_PEM_KEY: /devopscorner/cicd/staging/credentials/base64_pem_key 19 | STORE_BASE64_SSH_CONFIG: /devopscorner/cicd/staging/credentials/base64_ssh_config 20 | STORE_BASE64_KNOWN_HOSTS: /devopscorner/cicd/staging/credentials/known_hosts 21 | STORE_BASE64_KUBECONFIG: /devopscorner/cicd/staging/credentials/base64_kube_config 22 | 23 | # ===================================== # 24 | # Ref: Pipeline Environment Variables # 25 | # ===================================== # 26 | variables: 27 | ENV_CICD: "dev" 28 | AWS_DEFAULT_REGION: "ap-southeast-1" 29 | INFRA_CICD: "terraform/environment/providers/aws/infra/resources" 30 | INFRA_CICD_PATH: "bookstore" 31 | INFRA_ECR_PATH: "devopscorner/bookstore" 32 | 33 | phases: 34 | pre_build: 35 | commands: 36 | # ======================= # 37 | # Setup Auth Repository # 38 | # ======================= # 39 | - mkdir -p ~/.ssh 40 | - echo "${STORE_BASE64_PUB_KEY}" | base64 -d > ~/.ssh/id_rsa.pub 41 | - echo "${STORE_BASE64_PRIV_KEY}" | base64 -d > ~/.ssh/id_rsa 42 | - echo "${STORE_BASE64_KNOWN_HOSTS}" | base64 -d > ~/.ssh/known_hosts 43 | - chmod 400 ~/.ssh/id_rsa* 44 | - chmod 644 ~/.ssh/known_hosts 45 | - eval "$(ssh-agent -s)" 46 | - ssh-add ~/.ssh/id_rsa 47 | - echo '- DONE -' 48 | build: 49 | commands: 50 | # ========================= # 51 | # Refactoring AWS Account # 52 | # ========================= # 53 | - cd ${CODEBUILD_SRC_DIR} && find ./ -type f -exec sed -i "s/YOUR_AWS_ACCOUNT/${STORE_AWS_ACCOUNT}/g" {} \; 54 | # ============= # 55 | # Build Image # 56 | # ============= # 57 | - make ecr-build-alpine ARGS=${STORE_AWS_ACCOUNT} CI_PATH=${INFRA_ECR_PATH} 58 | # ============== # 59 | # Unit Testing # 60 | # ============== # 61 | # - make unit-test 62 | # ============ # 63 | # Tags Image # 64 | # ============ # 65 | - make ecr-tag-alpine ARGS=${STORE_AWS_ACCOUNT} CI_PATH=${INFRA_ECR_PATH} 66 | - docker images --format "{{.Repository}}:{{.Tag}}" | grep ${INFRA_ECR_PATH} 67 | # ============ # 68 | # Push Image # 69 | # ============ # 70 | - make ecr-push-alpine ARGS=${STORE_AWS_ACCOUNT} TAGS=${INFRA_ECR_PATH} 71 | 72 | artifacts: 73 | files: 74 | - _infra/* 75 | - .aws/* 76 | - docs/* 77 | - src/* 78 | - dockerhub-build.sh 79 | - dockerhub-push.sh 80 | - dockerhub-tag.sh 81 | - ecr-build.sh 82 | - ecr-push.sh 83 | - ecr-tag.sh 84 | - Makefile 85 | name: "artifact-$(date '+%Y%m%d-%H%M%S')" -------------------------------------------------------------------------------- /.aws/buildspec-deploy-nosource.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | # ==================== # 5 | # Ref: SECRET CONFIG # 6 | # ==================== # 7 | parameter-store: 8 | BUILDNUMBER: /devopscorner/cicd/staging/repo/bookstore/buildnumber 9 | STORE_AWS_ACCOUNT: /devopscorner/cicd/staging/credentials/aws_account 10 | STORE_AWS_ACCESS_KEY: /devopscorner/cicd/staging/credentials/aws_access_key 11 | STORE_AWS_SECRET_KEY: /devopscorner/cicd/staging/credentials/aws_secret_key 12 | STORE_REPO_URL: /devopscorner/cicd/staging/repo/bookstore/url 13 | STORE_REPO_BRANCH: /devopscorner/cicd/staging/repo/bookstore/branch 14 | STORE_REPO_FOLDER: /devopscorner/cicd/staging/repo/bookstore/folder 15 | STORE_EKS_CLUSTER: /devopscorner/cicd/staging/eks_cluster 16 | STORE_BASE64_PUB_KEY: /devopscorner/cicd/staging/credentials/base64_pub_key 17 | STORE_BASE64_PRIV_KEY: /devopscorner/cicd/staging/credentials/base64_priv_key 18 | STORE_BASE64_PEM_KEY: /devopscorner/cicd/staging/credentials/base64_pem_key 19 | STORE_BASE64_SSH_CONFIG: /devopscorner/cicd/staging/credentials/base64_ssh_config 20 | STORE_BASE64_KNOWN_HOSTS: /devopscorner/cicd/staging/credentials/known_hosts 21 | STORE_BASE64_KUBECONFIG: /devopscorner/cicd/staging/credentials/base64_kube_config 22 | 23 | # ===================================== # 24 | # Ref: Pipeline Environment Variables # 25 | # ===================================== # 26 | variables: 27 | ENV_CICD: "dev" 28 | AWS_DEFAULT_REGION: "ap-southeast-1" 29 | INFRA_CICD: "terraform/environment/providers/aws/infra/resources" 30 | INFRA_CICD_PATH: "bookstore" 31 | INFRA_ECR_PATH: "devopscorner/bookstore" 32 | 33 | phases: 34 | pre_build: 35 | commands: 36 | # ======================= # 37 | # Setup Auth Repository # 38 | # ======================= # 39 | - mkdir -p ~/.ssh 40 | - echo "${STORE_BASE64_PUB_KEY}" | base64 -d > ~/.ssh/id_rsa.pub 41 | - echo "${STORE_BASE64_PRIV_KEY}" | base64 -d > ~/.ssh/id_rsa 42 | - echo "${STORE_BASE64_KNOWN_HOSTS}" | base64 -d > ~/.ssh/known_hosts 43 | - echo "${STORE_BASE64_SSH_CONFIG}" | base64 -d > ~/.ssh/config 44 | - chmod 400 ~/.ssh/id_rsa* 45 | - chmod 400 ~/.ssh/config* 46 | - chmod 644 ~/.ssh/known_hosts 47 | - eval "$(ssh-agent -s)" 48 | - ssh-add ~/.ssh/id_rsa 49 | # =========================== # 50 | # Using Spesific Repository # 51 | # =========================== # 52 | - GIT_SSH_COMMAND='ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes -F /dev/null' git clone --depth 5 ${STORE_REPO_URL} 53 | - echo '- DONE -' 54 | build: 55 | commands: 56 | # ========================= # 57 | # Refactoring AWS Account # 58 | # ========================= # 59 | - cd ${STORE_REPO_FOLDER} && find ./ -type f -exec sed -i "s/YOUR_AWS_ACCOUNT/${STORE_AWS_ACCOUNT}/g" {} \; 60 | # ================== # 61 | # Helm Repo Update # 62 | # ================== # 63 | - AWS_REGION=${AWS_DEFAULT_REGION} helm repo add devopscorner-staging s3://devopscorner-helm-chart/staging 64 | - AWS_REGION=${AWS_DEFAULT_REGION} helm repo add devopscorner-prod s3://devopscorner-helm-chart/prod 65 | - helm repo update 66 | # ============ # 67 | # Deploy K8S # 68 | # ============ # 69 | - cd _infra/${ENV_CICD} 70 | - aws eks update-kubeconfig --region ${AWS_DEFAULT_REGION} --name ${STORE_EKS_CLUSTER} 71 | - kubectl version 72 | - kubectl config use-context arn:aws:eks:${AWS_DEFAULT_REGION}:${STORE_AWS_ACCOUNT}:cluster/${STORE_EKS_CLUSTER} 73 | - kubectl get ns -A 74 | - helmfile --version 75 | - helmfile -f helm-template.yml apply 76 | - echo '-- ALL DONE --' 77 | 78 | artifacts: 79 | files: 80 | - _infra/* 81 | - .aws/* 82 | - docs/* 83 | - src/* 84 | - dockerhub-build.sh 85 | - dockerhub-push.sh 86 | - dockerhub-tag.sh 87 | - ecr-build.sh 88 | - ecr-push.sh 89 | - ecr-tag.sh 90 | - Makefile 91 | name: "artifact-$(date '+%Y%m%d-%H%M%S')" -------------------------------------------------------------------------------- /.aws/buildspec-deploy.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | # ==================== # 5 | # Ref: SECRET CONFIG # 6 | # ==================== # 7 | parameter-store: 8 | BUILDNUMBER: /devopscorner/cicd/staging/repo/bookstore/buildnumber 9 | STORE_AWS_ACCOUNT: /devopscorner/cicd/staging/credentials/aws_account 10 | STORE_AWS_ACCESS_KEY: /devopscorner/cicd/staging/credentials/aws_access_key 11 | STORE_AWS_SECRET_KEY: /devopscorner/cicd/staging/credentials/aws_secret_key 12 | STORE_REPO_URL: /devopscorner/cicd/staging/repo/bookstore/url 13 | STORE_REPO_BRANCH: /devopscorner/cicd/staging/repo/bookstore/branch 14 | STORE_REPO_FOLDER: /devopscorner/cicd/staging/repo/bookstore/folder 15 | STORE_EKS_CLUSTER: /devopscorner/cicd/staging/eks_cluster 16 | STORE_BASE64_PUB_KEY: /devopscorner/cicd/staging/credentials/base64_pub_key 17 | STORE_BASE64_PRIV_KEY: /devopscorner/cicd/staging/credentials/base64_priv_key 18 | STORE_BASE64_PEM_KEY: /devopscorner/cicd/staging/credentials/base64_pem_key 19 | STORE_BASE64_SSH_CONFIG: /devopscorner/cicd/staging/credentials/base64_ssh_config 20 | STORE_BASE64_KNOWN_HOSTS: /devopscorner/cicd/staging/credentials/known_hosts 21 | STORE_BASE64_KUBECONFIG: /devopscorner/cicd/staging/credentials/base64_kube_config 22 | 23 | # ===================================== # 24 | # Ref: Pipeline Environment Variables # 25 | # ===================================== # 26 | variables: 27 | ENV_CICD: "dev" 28 | AWS_DEFAULT_REGION: "ap-southeast-1" 29 | INFRA_CICD: "terraform/environment/providers/aws/infra/resources" 30 | INFRA_CICD_PATH: "bookstore" 31 | INFRA_ECR_PATH: "devopscorner/bookstore" 32 | 33 | phases: 34 | pre_build: 35 | commands: 36 | # ======================= # 37 | # Setup Auth Repository # 38 | # ======================= # 39 | - mkdir -p ~/.ssh 40 | - mkdir -p ~/.kube 41 | - echo "${STORE_BASE64_PUB_KEY}" | base64 -d > ~/.ssh/id_rsa.pub 42 | - echo "${STORE_BASE64_PRIV_KEY}" | base64 -d > ~/.ssh/id_rsa 43 | - echo "${STORE_BASE64_KNOWN_HOSTS}" | base64 -d > ~/.ssh/known_hosts 44 | - echo "${STORE_BASE64_KUBECONFIG}" | base64 -d > ~/.kube/config 45 | - chmod 400 ~/.ssh/id_rsa* 46 | - chmod 400 ~/.kube/config* 47 | - chmod 644 ~/.ssh/known_hosts 48 | - eval "$(ssh-agent -s)" 49 | - ssh-add ~/.ssh/id_rsa 50 | - echo '- DONE -' 51 | build: 52 | commands: 53 | # ========================= # 54 | # Refactoring AWS Account # 55 | # ========================= # 56 | - cd ${CODEBUILD_SRC_DIR} && find ./ -type f -exec sed -i "s/YOUR_AWS_ACCOUNT/${STORE_AWS_ACCOUNT}/g" {} \; 57 | # ================== # 58 | # Helm Repo Update # 59 | # ================== # 60 | - AWS_REGION=${AWS_DEFAULT_REGION} helm repo add devopscorner-staging s3://devopscorner-helm-chart/staging 61 | - AWS_REGION=${AWS_DEFAULT_REGION} helm repo add devopscorner-prod s3://devopscorner-helm-chart/prod 62 | - helm repo update 63 | # ============ # 64 | # Deploy K8S # 65 | # ============ # 66 | - cd _infra/${ENV_CICD} 67 | - aws eks update-kubeconfig --region ${AWS_DEFAULT_REGION} --name ${STORE_EKS_CLUSTER} 68 | - kubectl version 69 | - kubectl config use-context arn:aws:eks:${AWS_DEFAULT_REGION}:${STORE_AWS_ACCOUNT}:cluster/${STORE_EKS_CLUSTER} 70 | - kubectl get ns -A 71 | - helmfile --version 72 | - helmfile -f helm-template.yml apply 73 | - echo '-- ALL DONE --' 74 | 75 | artifacts: 76 | files: 77 | - _infra/* 78 | - .aws/* 79 | - docs/* 80 | - src/* 81 | - dockerhub-build.sh 82 | - dockerhub-push.sh 83 | - dockerhub-tag.sh 84 | - ecr-build.sh 85 | - ecr-push.sh 86 | - ecr-tag.sh 87 | - Makefile 88 | name: "artifact-$(date '+%Y%m%d-%H%M%S')" -------------------------------------------------------------------------------- /.aws/cicd-aws-codepipeline.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | phases: 4 | install: 5 | runtime-versions: 6 | docker: 19 7 | build: 8 | commands: 9 | - go build -o app 10 | - | 11 | if [[ "$CODEBUILD_WEBHOOK_TRIGGER" == "branch/main" ]]; then 12 | semver=1.0.0-${CODEBUILD_SOURCE_VERSION} 13 | elif [[ "$CODEBUILD_WEBHOOK_TRIGGER" == "branch/features/"* ]]; then 14 | semver=1.0.0-${CODEBUILD_WEBHOOK_TRIGGER#branch/features/}.${CODEBUILD_SOURCE_VERSION} 15 | elif [[ "$CODEBUILD_WEBHOOK_TRIGGER" == "branch/bugfix/"* ]]; then 16 | semver=1.1.0-${CODEBUILD_WEBHOOK_TRIGGER#branch/bugfix/}.${CODEBUILD_SOURCE_VERSION} 17 | elif [[ "$CODEBUILD_WEBHOOK_TRIGGER" == "branch/hotfix/"* ]]; then 18 | semver=1.1.1-${CODEBUILD_WEBHOOK_TRIGGER#branch/hotfix/}.${CODEBUILD_SOURCE_VERSION} 19 | fi 20 | echo "Semantic version: $semver" 21 | echo "imageTag=$semver" >> $CODEBUILD_SRC_DIR/variables.env 22 | $(aws ecr get-login --no-include-email --region $AWS_REGION) 23 | docker build -t $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver . 24 | docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver 25 | docker tag $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:latest 26 | docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:latest 27 | post_build: 28 | commands: 29 | - | 30 | echo "Deploying to Kubernetes using Helm" 31 | curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash 32 | helmfile sync 33 | 34 | artifacts: 35 | files: 36 | - _infra/* 37 | - .aws/* 38 | - docs/* 39 | - src/* 40 | - dockerhub-build.sh 41 | - dockerhub-push.sh 42 | - dockerhub-tag.sh 43 | - ecr-build.sh 44 | - ecr-push.sh 45 | - ecr-tag.sh 46 | - Makefile 47 | name: "artifact-$(date '+%Y%m%d-%H%M%S')" 48 | discard-paths: yes 49 | -------------------------------------------------------------------------------- /.aws/known_hosts: -------------------------------------------------------------------------------- 1 | # GITLAB 2 | ## >> ssh-keyscan -H 'gitlab.com' >> ~/.ssh/known_hosts 3 | ## >> ssh -T git@gitlab.com 4 | 5 | # GITHUB 6 | ## >> ssh-keyscan -H 'github.com' >> ~/.ssh/known_hosts 7 | ## >> ssh -T git@github.com 8 | 9 | # BITBUCKET 10 | ## >> ssh-keyscan -H 'bitbucket.org' >> ~/.ssh/known_hosts 11 | ## >> ssh -T git@bitbucket.org 12 | 13 | # Azure DevOps 14 | ## >> ssh-keyscan -H 'vs-ssh.visualstudio.com' >> ~/.ssh/known_hosts 15 | ## >> ssh-keyscan -H '[organization]@vs-ssh.visualstudio.com' >> ~/.ssh/known_hosts 16 | ## >> ssh -T vs-ssh.visualstudio.com 17 | ## >> ssh -T [organization]@vs-ssh.visualstudio.com 18 | -------------------------------------------------------------------------------- /.aws/ssh-config: -------------------------------------------------------------------------------- 1 | ############################# 2 | ### Repository CodeCommit ### 3 | ############################# 4 | Host git-codecommit.*.amazonaws.com 5 | User YOUR_CODECOMIT_SSH_ID 6 | IdentityFile ~/.ssh/id_rsa 7 | IdentitiesOnly yes 8 | 9 | ############################### 10 | ### Repository Azure DevOps ### 11 | ############################### 12 | Host ssh.dev.azure.com 13 | User git 14 | #PubkeyAcceptedAlgorithms +ssh-rsa 15 | #HostkeyAlgorithms +ssh-rsa 16 | IdentityFile ~/.ssh/id_rsa 17 | IdentitiesOnly yes -------------------------------------------------------------------------------- /.azure-devops/cicd-azure-devops.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | branches: 3 | include: 4 | - features/* 5 | - bugfix/* 6 | - hotfix/* 7 | 8 | pool: 9 | vmImage: 'ubuntu-latest' 10 | 11 | variables: 12 | imageName: 'devopcorner/bookstore' 13 | ecrRegistry: '0987612345.dkr.ecr.ap-southeast-1.amazonaws.com' 14 | helmReleaseName: 'bookstore' 15 | 16 | steps: 17 | - script: | 18 | go build -o app 19 | displayName: 'Build Go Application' 20 | 21 | - script: | 22 | if [[ "$(Build.SourceBranch)" == "refs/heads/features/"* ]]; then 23 | semver=1.0.0-$(Build.SourceBranchName).$(Build.SourceVersion) 24 | elif [[ "$(Build.SourceBranch)" == "refs/heads/bugfix/"* ]]; then 25 | semver=1.1.0-$(Build.SourceBranchName).$(Build.SourceVersion) 26 | elif [[ "$(Build.SourceBranch)" == "refs/heads/hotfix/"* ]]; then 27 | semver=1.1.1-$(Build.SourceBranchName).$(Build.SourceVersion) 28 | fi 29 | echo "Semantic version: $semver" 30 | echo "##vso[task.setvariable variable=imageTag]$semver" 31 | displayName: 'Set Semantic Version' 32 | 33 | - task: Docker@2 34 | inputs: 35 | containerRegistry: 'ECR' 36 | repository: $(imageName) 37 | command: 'build' 38 | Dockerfile: '$(System.DefaultWorkingDirectory)/Dockerfile' 39 | tags: | 40 | $(imageTag) 41 | $(Build.SourceBranchName)-latest 42 | latest 43 | displayName: 'Build and Push Docker Image' 44 | 45 | - task: HelmInstaller@1 46 | inputs: 47 | helmVersionToInstall: 'v3.7.0' 48 | displayName: 'Install Helm' 49 | 50 | - script: | 51 | helmfile sync 52 | workingDirectory: '$(System.DefaultWorkingDirectory)/helm' 53 | displayName: 'Deploy to EKS using Helmfile' 54 | -------------------------------------------------------------------------------- /.azure-devops/repo-sync-from-azure-devops.yml: -------------------------------------------------------------------------------- 1 | # Pipeline to automatically mirror 2 | # an Azure DevOps repository in AWS CodeCommit 3 | 4 | # Trigger on all branches 5 | trigger: 6 | - "*" 7 | 8 | # Use latest Ubuntu image 9 | pool: 10 | vmImage: "ubuntu-latest" 11 | 12 | # Pipeline 13 | steps: 14 | - checkout: none 15 | - script: | 16 | 17 | # Install urlencode function to encode reserved characters in passwords 18 | sudo apt-get install gridsite-clients 19 | 20 | # Create local mirror of Azure DevOps repository 21 | git clone --mirror https://${AZURE_GIT_USERNAME}:$(urlencode ${AZURE_GIT_PASSWORD})@${AZURE_REPO_URL} repo-mirror 22 | 23 | # Sync AWS CodeCommit repository 24 | cd repo-mirror 25 | git push --mirror https://${AWS_GIT_USERNAME}:$(urlencode ${AWS_GIT_PASSWORD})@${AWS_REPO_URL} 26 | 27 | displayName: "Sync Repository with AWS CodeCommit" 28 | env: 29 | AZURE_REPO_URL: $(AZURE_REPO_URL) 30 | AZURE_GIT_USERNAME: $(AZURE_GIT_USERNAME) 31 | AZURE_GIT_PASSWORD: $(AZURE_GIT_PASSWORD) 32 | AWS_REPO_URL: $(AWS_REPO_URL) 33 | AWS_GIT_USERNAME: $(AWS_GIT_USERNAME) 34 | AWS_GIT_PASSWORD: $(AWS_GIT_PASSWORD) 35 | -------------------------------------------------------------------------------- /.bitbucket/cicd-bitbucket.yml: -------------------------------------------------------------------------------- 1 | pipelines: 2 | default: 3 | - step: 4 | name: Build and Deploy 5 | image: golang:1.17 6 | script: 7 | - go build -o app 8 | - | 9 | if [[ "$BITBUCKET_BRANCH" == "features/"* ]]; then 10 | semver=1.0.0-${BITBUCKET_BRANCH#features/}.${BITBUCKET_COMMIT:0:8} 11 | elif [[ "$BITBUCKET_BRANCH" == "bugfix/"* ]]; then 12 | semver=1.1.0-${BITBUCKET_BRANCH#bugfix/}.${BITBUCKET_COMMIT:0:8} 13 | elif [[ "$BITBUCKET_BRANCH" == "hotfix/"* ]]; then 14 | semver=1.1.1-${BITBUCKET_BRANCH#hotfix/}.${BITBUCKET_COMMIT:0:8} 15 | fi 16 | echo "Semantic version: $semver" 17 | echo "imageTag=$semver" >> $BITBUCKET_CLONE_DIR/variables.env 18 | aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com 19 | docker build -t $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver . 20 | docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver 21 | docker tag $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:latest 22 | docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:latest 23 | artifacts: 24 | - app 25 | services: 26 | - docker 27 | caches: 28 | - docker 29 | - go 30 | deployment: production 31 | trigger: manual 32 | environment: 33 | name: production 34 | url: $BITBUCKET_DEPLOYMENT_ENVIRONMENT_URL 35 | -------------------------------------------------------------------------------- /.bitbucket/repo-sync-from-bitbucket.yml: -------------------------------------------------------------------------------- 1 | # Pipeline to automatically mirror 2 | # a BitBucket repository in AWS CodeCommit 3 | 4 | definitions: 5 | services: 6 | synch_repo: 7 | environment: 8 | BITBUCKET_REPO_URL: $(BITBUCKET_REPO_URL) 9 | BITBUCKET_GIT_USERNAME: $(BITBUCKET_GIT_USERNAME) 10 | BITBUCKET_GIT_PASSWORD: $(BITBUCKET_GIT_PASSWORD) 11 | AWS_REPO_URL: $(AWS_REPO_URL) 12 | AWS_GIT_USERNAME: $(AWS_GIT_USERNAME) 13 | AWS_GIT_PASSWORD: $(AWS_GIT_PASSWORD) 14 | 15 | synchonize: &synchonize 16 | step: 17 | name: Sync Repository with AWS CodeCommit 18 | deployment: staging 19 | image: ubuntu-latest 20 | trigger: manual 21 | script: 22 | # Install urlencode function to encode reserved characters in passwords 23 | - sudo apt-get install gridsite-clients 24 | # Create local mirror of Azure DevOps repository 25 | - git clone --mirror https://${BITBUCKET_GIT_USERNAME}:$(urlencode ${BITBUCKET_GIT_PASSWORD})@${BITBUCKET_REPO_URL} repo-mirror 26 | # Sync AWS CodeCommit repository 27 | - cd repo-mirror 28 | - git push --mirror https://${AWS_GIT_USERNAME}:$(urlencode ${AWS_GIT_PASSWORD})@${AWS_REPO_URL} 29 | services: 30 | - synch_repo 31 | 32 | pipelines: 33 | default: 34 | - <<: *synchonize 35 | -------------------------------------------------------------------------------- /.circleci/cicd-circleci.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | jobs: 3 | build-and-deploy: 4 | docker: 5 | - image: circleci/golang:1.19.5 6 | 7 | environment: 8 | AWS_REGION: ap-southeast-1 9 | AWS_ACCOUNT_ID: 0987612345 10 | IMAGE_NAME: devopscorner/bookstore 11 | 12 | steps: 13 | - checkout 14 | - run: 15 | name: Build and push Docker image 16 | command: | 17 | go build -o app 18 | if [[ "$CIRCLE_BRANCH" == "main" ]]; then 19 | semver=1.0.0-${CIRCLE_SHA1:0:8} 20 | elif [[ "$CIRCLE_BRANCH" == "features/"* ]]; then 21 | semver=1.0.0-${CIRCLE_BRANCH#features/}.${CIRCLE_SHA1:0:8} 22 | elif [[ "$CIRCLE_BRANCH" == "bugfix/"* ]]; then 23 | semver=1.1.0-${CIRCLE_BRANCH#bugfix/}.${CIRCLE_SHA1:0:8} 24 | elif [[ "$CIRCLE_BRANCH" == "hotfix/"* ]]; then 25 | semver=1.1.1-${CIRCLE_BRANCH#hotfix/}.${CIRCLE_SHA1:0:8} 26 | fi 27 | echo "Semantic version: $semver" 28 | echo "imageTag=$semver" >> $HOME/variables.env 29 | aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com 30 | docker build -t $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver . 31 | docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver 32 | docker tag $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:latest 33 | docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:latest 34 | 35 | - run: 36 | name: Deploy to Kubernetes using Helmfile 37 | command: | 38 | curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash 39 | helmfile sync 40 | 41 | - store_artifacts: 42 | path: app 43 | 44 | workflows: 45 | build-and-deploy: 46 | jobs: 47 | - build-and-deploy: 48 | requires: 49 | - checkout 50 | filters: 51 | branches: 52 | only: 53 | - main 54 | - /^features\/.*$/ 55 | - /^bugfix\/.*$/ 56 | - /^hotfix\/.*$/ 57 | -------------------------------------------------------------------------------- /.codecatalyst/workflows/Workflow_GitHubAction.yaml: -------------------------------------------------------------------------------- 1 | Name: Workflow_GitHubActions_Deployment 2 | SchemaVersion: "1.0" 3 | 4 | # Optional - Set automatic triggers. 5 | Triggers: 6 | - Type: PUSH 7 | Branches: 8 | - master 9 | - "release/*" 10 | - Type: PULLREQUEST 11 | Branches: 12 | - "features/*" 13 | - "bugfix/*" 14 | - "hotfix/*" 15 | Events: 16 | - OPEN 17 | - REVISION 18 | 19 | # Required - Define action configurations. 20 | Actions: 21 | GitHubActions_Deployment: 22 | Identifier: aws/github-actions-runner@v1 23 | Inputs: 24 | Sources: 25 | - WorkflowSource 26 | Configuration: 27 | Steps: 28 | - name: Build Container GO Apps 29 | run: | 30 | GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD) 31 | COMMIT_HASH=$(git log -1 --format=format:"%H") 32 | 33 | latestTag=$(git describe --tags `git rev-list --tags --max-count=1`) 34 | if [[ -z "$latestTag" ]]; then 35 | latestTag=1.0.0 36 | fi 37 | 38 | if [[ "$GIT_BRANCH" == "features/"* ]]; then 39 | semver="$latestTag-features.${COMMIT_HASH}" 40 | elif [[ "$GIT_BRANCH" == "bugfix/"* ]]; then 41 | semver="$latestTag-bugfix.${COMMIT_HASH}" 42 | elif [[ "$GIT_BRANCH" == "hotfix/"* ]]; then 43 | semver="$latestTag-beta.${COMMIT_HASH}" 44 | else 45 | semver="$latestTag.${COMMIT_HASH}" 46 | fi 47 | 48 | if [[ -z "$semver" ]]; then 49 | ## DockerHub 50 | ## ./dockerhub-build.sh Dockerfile devopscorner/bookstore alpine $COMMIT_HASH 51 | ./ecr-build.sh "${Secrets.AWS_ACCOUNT_ID}" Dockerfile devopscorner/bookstore alpine $COMMIT_HASH 52 | else 53 | ## DockerHub 54 | ## ./dockerhub-build.sh Dockerfile devopscorner/bookstore alpine $semver 55 | ## ECR 56 | ./ecr-build.sh "${Secrets.AWS_ACCOUNT_ID}" Dockerfile devopscorner/bookstore alpine $semver 57 | fi 58 | - name: List Container Images 59 | run: | 60 | docker images | grep "devopscorner/bookstore" 61 | - name: Push to ECR (Container Registry) 62 | run: | 63 | ./ecr-push.sh "${Secrets.AWS_ACCOUNT_ID}" devopscorner/bookstore alpine 64 | - name: Deploy to ECS 65 | run: | 66 | echo "Deploy to ECS cluster..." 67 | Outputs: 68 | Artifacts: 69 | - Name: "MyArtifact" 70 | Files: 71 | - "_infra/**" 72 | - ".aws/**" 73 | - ".codecatalyst/workflows/**" 74 | - "docs/**" 75 | - "docker-compose.yml" 76 | - "Dockerfile" 77 | - "Dockerfile.alpine-3.15" 78 | - "Dockerfile.alpine-3.16" 79 | - "Dockerfile.alpine-3.17" 80 | - "dockerhub-build.sh" 81 | - "dockerhub-push.sh" 82 | - "dockerhub-tag.sh" 83 | - "ecr-build.sh" 84 | - "ecr-push.sh" 85 | - "ecr-pull.sh" 86 | - "ecr-tag.sh" 87 | - "entrypoint.sh" 88 | - "git-clone.sh" 89 | - "Makefile" 90 | - "run-docker.sh" 91 | - "start-build.sh" 92 | - "src/**" 93 | Compute: 94 | Type: EC2 95 | Fleet: Linux.x86-64.Large 96 | Environment: 97 | Connections: 98 | - Role: CodeCatalystPreviewDevelopmentAdministrator-5nxysr 99 | Name: "YOUR_AWS_ACCOUNT" 100 | Name: staging-dev 101 | DeployToECS: 102 | DependsOn: 103 | - GitHubActions_Deployment 104 | Identifier: aws/ecs-deploy@v1 105 | Environment: 106 | Connections: 107 | - Role: CodeCatalystPreviewDevelopmentAdministrator-5nxysr 108 | Name: "YOUR_AWS_ACCOUNT" 109 | Name: staging-dev 110 | Inputs: 111 | Sources: 112 | - WorkflowSource 113 | Artifacts: 114 | - MyArtifact 115 | Configuration: 116 | region: us-west-2 117 | cluster: codecatalyst-ecs-cluster 118 | service: bookstore-ecs-service 119 | task-definition: .codecatalyst/workflows/taskdef.json 120 | -------------------------------------------------------------------------------- /.codecatalyst/workflows/taskdef.json: -------------------------------------------------------------------------------- 1 | { 2 | "executionRoleArn": "arn:aws:iam::YOUR_AWS_ACCOUNT:role/codecatalyst-ecs-task-execution-role", 3 | "containerDefinitions": [ 4 | { 5 | "name": "codecatalyst-ecs-container", 6 | "image": "YOUR_AWS_ACCOUNT.dkr.ecr.us-west-2.amazonaws.com/devopscorner/bookstore-codecatalyst:alpine", 7 | "essential": true, 8 | "portMappings": [ 9 | { 10 | "hostPort": 8080, 11 | "protocol": "tcp", 12 | "containerPort": 8080 13 | } 14 | ] 15 | } 16 | ], 17 | "requiresCompatibilities": [ 18 | "FARGATE" 19 | ], 20 | "cpu": "256", 21 | "family": "bookstore-ecs-task-def", 22 | "memory": "512", 23 | "networkMode": "awsvpc" 24 | } -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | bin/** 2 | build/** 3 | logs/** 4 | log/** 5 | src/.env 6 | src/*.db 7 | .env 8 | *.db 9 | -------------------------------------------------------------------------------- /.droneci/cicd-droneci.yml: -------------------------------------------------------------------------------- 1 | kind: pipeline 2 | type: docker 3 | name: bookstore 4 | 5 | platform: 6 | os: linux 7 | arch: amd64 8 | 9 | steps: 10 | - name: build 11 | image: golang:1.19.5 12 | commands: 13 | - go mod download 14 | - if [ "${DRONE_BRANCH}" = "main" ]; then 15 | semver=1.0.0-${DRONE_COMMIT_SHA:0:8}; 16 | elif [[ "${DRONE_BRANCH}" == "features/"* ]]; then 17 | semver=1.0.0-${DRONE_BRANCH#features/}.${DRONE_COMMIT_SHA:0:8}; 18 | elif [[ "${DRONE_BRANCH}" == "bugfix/"* ]]; then 19 | semver=1.1.0-${DRONE_BRANCH#bugfix/}.${DRONE_COMMIT_SHA:0:8}; 20 | elif [[ "${DRONE_BRANCH}" == "hotfix/"* ]]; then 21 | semver=1.1.1-${DRONE_BRANCH#hotfix/}.${DRONE_COMMIT_SHA:0:8}; 22 | fi 23 | - echo "Semantic version: $semver" 24 | - docker build -t ${PLUGIN_REGISTRY}/${PLUGIN_REPO}:${semver} . 25 | - docker tag ${PLUGIN_REGISTRY}/${PLUGIN_REPO}:${semver} ${PLUGIN_REGISTRY}/${PLUGIN_REPO}:latest 26 | environment: 27 | - PLUGIN_REGISTRY=your-registry-url 28 | - PLUGIN_REPO=bookstore 29 | when: 30 | event: 31 | - push 32 | - tag 33 | 34 | - name: publish 35 | image: plugins/ecr 36 | settings: 37 | region: ap-southeast-1 38 | access_key: 39 | from_secret: aws_access_key 40 | secret_key: 41 | from_secret: aws_secret_key 42 | repo: ${PLUGIN_REGISTRY}/${PLUGIN_REPO} 43 | tags: latest,${semver} 44 | when: 45 | event: 46 | - push 47 | - tag 48 | 49 | - name: deploy 50 | image: dtzar/helm-kubectl 51 | settings: 52 | kubernetes_server: your-kubernetes-server 53 | kubernetes_cert: 54 | from_secret: kubernetes_cert 55 | kubernetes_token: 56 | from_secret: kubernetes_token 57 | kubernetes_namespace: bookstore 58 | command: helmfile sync 59 | when: 60 | event: 61 | - push 62 | - tag 63 | -------------------------------------------------------------------------------- /.github/workflows/cicd-github.yml: -------------------------------------------------------------------------------- 1 | name: CI/CD Pipeline 2 | 3 | on: 4 | push: 5 | branches: 6 | - features/* 7 | - bugfix/* 8 | - hotfix/* 9 | 10 | jobs: 11 | build-and-deploy: 12 | runs-on: ubuntu-latest 13 | 14 | env: 15 | imageName: 'devopcorner/bookstore' 16 | ecrRegistry: '0987612345.dkr.ecr.ap-southeast-1.amazonaws.com' 17 | helmReleaseName: 'bookstore' 18 | 19 | steps: 20 | - name: Checkout Code 21 | uses: actions/checkout@v2 22 | with: 23 | fetch-depth: 0 24 | 25 | - name: Set up Go 26 | uses: actions/setup-go@v2 27 | with: 28 | go-version: 1.17 29 | 30 | - name: Build Go Application 31 | run: go build -o app 32 | 33 | - name: Set Semantic Version 34 | run: | 35 | if [[ "$GITHUB_REF" == "refs/heads/features/"* ]]; then 36 | semver=1.0.0-${GITHUB_REF#refs/heads/features/}.${GITHUB_SHA::8} 37 | elif [[ "$GITHUB_REF" == "refs/heads/bugfix/"* ]]; then 38 | semver=1.1.0-${GITHUB_REF#refs/heads/bugfix/}.${GITHUB_SHA::8} 39 | elif [[ "$GITHUB_REF" == "refs/heads/hotfix/"* ]]; then 40 | semver=1.1.1-${GITHUB_REF#refs/heads/hotfix/}.${GITHUB_SHA::8} 41 | fi 42 | echo "::set-env name=imageTag::$semver" 43 | 44 | - name: Build and Push Docker Image 45 | uses: docker/build-push-action@v2 46 | with: 47 | context: . 48 | push: true 49 | tags: | 50 | ${{ env.imageName }}:${{ env.imageTag }} 51 | ${{ env.imageName }}:${{ github.ref }}-latest 52 | ${{ env.imageName }}:latest 53 | registry: ${{ env.ecrRegistry }} 54 | username: ${{ secrets.ECR_USERNAME }} 55 | password: ${{ secrets.ECR_PASSWORD }} 56 | 57 | - name: Install Helm 58 | uses: appleboy/ssh-action@master 59 | with: 60 | host: ${{ secrets.EKS_HOST }} 61 | username: ${{ secrets.EKS_USERNAME }} 62 | key: ${{ secrets.EKS_PRIVATE_KEY }} 63 | script: | 64 | curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash 65 | 66 | - name: Deploy to EKS using Helmfile 67 | uses: appleboy/ssh-action@master 68 | with: 69 | host: ${{ secrets.EKS_HOST }} 70 | username: ${{ secrets.EKS_USERNAME }} 71 | key: ${{ secrets.EKS_PRIVATE_KEY }} 72 | script: | 73 | helmfile sync 74 | -------------------------------------------------------------------------------- /.github/workflows/clone.yml: -------------------------------------------------------------------------------- 1 | name: GitHub Clone Count for 14 days at every 12 hours 2 | 3 | # Controls when the action will run. 4 | on: 5 | schedule: 6 | - cron: "0 */12 * * *" 7 | # Allows you to run this workflow manually from the Actions tab 8 | workflow_dispatch: 9 | 10 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 11 | jobs: 12 | # This workflow contains a single job called "build" 13 | build: 14 | # The type of runner that the job will run on 15 | runs-on: ubuntu-latest 16 | 17 | # Steps represent a sequence of tasks that will be executed as part of the job 18 | steps: 19 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 20 | - uses: actions/checkout@v2 21 | 22 | - name: Parse clone count using REST API 23 | run: | 24 | curl --user "${{ github.actor }}:${{ secrets.SECRET_TOKEN }}" \ 25 | -H "Accept: application/vnd.github.v3+json" \ 26 | https://api.github.com/repos/${{ github.repository }}/traffic/clones \ 27 | > clone.json 28 | 29 | - name: Add to git repo 30 | run: | 31 | git add . 32 | git config --global user.name "GitHub Action" 33 | git config --global user.email "action@github.com" 34 | git commit -m "Automated clone.json update" 35 | 36 | - name: Push 37 | uses: ad-m/github-push-action@master 38 | with: 39 | github_token: ${{ secrets.GITHUB_TOKEN }} 40 | -------------------------------------------------------------------------------- /.github/workflows/repo-sync-from-github.yml: -------------------------------------------------------------------------------- 1 | # Pipeline to automatically mirror 2 | # a GitHub repository in AWS CodeCommit 3 | 4 | name: Sync Repository with AWS CodeCommit 5 | 6 | on: 7 | workflow_dispatch 8 | 9 | env: 10 | GITHUB_REPO_URL: ${{ secrets.GITHUB_REPO_URL }} 11 | GITHUB_GIT_USERNAME: ${{ secrets.GITHUB_GIT_USERNAME }} 12 | GITHUB_GIT_PASSWORD: ${{ secrets.GITHUB_GIT_PASSWORD }} 13 | AWS_REPO_URL: ${{ secrets.AWS_REPO_URL }} 14 | AWS_GIT_USERNAME: ${{ secrets.AWS_GIT_USERNAME }} 15 | AWS_GIT_PASSWORD: ${{ secrets.AWS_GIT_PASSWORD }} 16 | 17 | jobs: 18 | build: 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | 24 | - name: Parse clone count using REST API 25 | run: | 26 | 27 | # Install urlencode function to encode reserved characters in passwords 28 | sudo apt-get install gridsite-clients 29 | 30 | # Create local mirror of Azure DevOps repository 31 | git clone --mirror https://${GITHUB_GIT_USERNAME}:$(urlencode ${GITHUB_GIT_PASSWORD})@${GITHUB_REPO_URL} repo-mirror 32 | 33 | # Sync AWS CodeCommit repository 34 | cd repo-mirror 35 | git push --mirror https://${AWS_GIT_USERNAME}:$(urlencode ${AWS_GIT_PASSWORD})@${AWS_REPO_URL} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #### Terraform .gitignore template 2 | ## Comment path that would like to get ignored 3 | 4 | ### Basic .gitignore 5 | # **/terraform.tfstate 6 | **/terraform.tfstate.backup 7 | **/terraform.tfplan 8 | **/.terraform/** 9 | **/terraform.tfstate.d/** 10 | **/*.tfplan 11 | **/terraform_plugins/** 12 | backend.hcl 13 | *.hcl 14 | *.lock.hcl 15 | # only for snd 16 | #backend.tf 17 | #*.tfvars 18 | 19 | ### https://raw.githubusercontent.com/hashicorp/terraform/master/.gitignore 20 | *.dll 21 | *.exe 22 | .DS_Store 23 | example.tf 24 | terraform.tfplan 25 | terraform.tfstate 26 | tf.out 27 | .vagrant/ 28 | *.backup 29 | ./*.tfstate 30 | errored.tfstate 31 | .terraform/ 32 | *.log 33 | *.bak 34 | *~ 35 | .*.swp 36 | .idea 37 | *.iml 38 | *.test 39 | *.iml 40 | #.envrc 41 | 42 | ### https://raw.github.com/github/gitignore/abad92dac5a4306f72242dae3bca6e277bce3615/Global/Vim.gitignore 43 | 44 | # swap 45 | [._]*.s[a-w][a-z] 46 | [._]s[a-w][a-z] 47 | # session 48 | Session.vim 49 | # temporary 50 | .netrwhist 51 | *~ 52 | # auto-generated tag files 53 | tags 54 | 55 | ### Ignore .DS_Store on MAC 56 | .DS_Store 57 | .DS_Store? 58 | 59 | ### Temporary dir for CodeDeploy 60 | build/ 61 | .build 62 | dummy.zip 63 | 64 | terraform.tfstate* 65 | *.tgz 66 | *.pem 67 | *.pub 68 | *.env 69 | *.icloud 70 | *.id_rsa 71 | logs 72 | .idea 73 | dist 74 | 75 | terraform/modules/** 76 | test/** 77 | 78 | *.db -------------------------------------------------------------------------------- /.gitlab/cicd-gitlab.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - build 3 | - deploy 4 | 5 | variables: 6 | imageName: 'devopcorner/bookstore' 7 | ecrRegistry: '0987612345.dkr.ecr.ap-southeast-1.amazonaws.com' 8 | helmReleaseName: 'bookstore' 9 | 10 | build: 11 | stage: build 12 | image: golang:1.17 13 | script: 14 | - go build -o app 15 | - | 16 | if [[ "$CI_COMMIT_REF_NAME" == "features/"* ]]; then 17 | semver=1.0.0-${CI_COMMIT_REF_NAME#features/}.${CI_COMMIT_SHORT_SHA} 18 | elif [[ "$CI_COMMIT_REF_NAME" == "bugfix/"* ]]; then 19 | semver=1.1.0-${CI_COMMIT_REF_NAME#bugfix/}.${CI_COMMIT_SHORT_SHA} 20 | elif [[ "$CI_COMMIT_REF_NAME" == "hotfix/"* ]]; then 21 | semver=1.1.1-${CI_COMMIT_REF_NAME#hotfix/}.${CI_COMMIT_SHORT_SHA} 22 | fi 23 | echo "Semantic version: $semver" 24 | echo "imageTag=$semver" >> $CI_ENVIRONMENT_URL/variables.env 25 | docker build -t $ecrRegistry/$imageName:$semver . 26 | docker push $ecrRegistry/$imageName:$semver 27 | docker tag $ecrRegistry/$imageName:$semver $ecrRegistry/$imageName:latest 28 | docker push $ecrRegistry/$imageName:latest 29 | artifacts: 30 | paths: 31 | - app 32 | 33 | deploy: 34 | stage: deploy 35 | image: alpine/helm:3.7.0 36 | script: 37 | - apk add --update openssh-client 38 | - ssh-keyscan $EKS_HOST >> ~/.ssh/known_hosts 39 | - ssh -i $EKS_PRIVATE_KEY $EKS_USERNAME@$EKS_HOST "curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash" 40 | - ssh -i $EKS_PRIVATE_KEY $EKS_USERNAME@$EKS_HOST "helmfile sync" 41 | environment: 42 | name: production 43 | url: $CI_ENVIRONMENT_URL 44 | dependencies: 45 | - build 46 | only: 47 | - main 48 | when: manual 49 | -------------------------------------------------------------------------------- /.gitlab/repo-sync-from-gitlab.yml: -------------------------------------------------------------------------------- 1 | # Pipeline to automatically mirror 2 | # a GitLab repository in AWS CodeCommit 3 | 4 | image: ubuntu-latest 5 | 6 | stages: 7 | - Sync Repository 8 | 9 | ### VARIABLES ### 10 | variables: 11 | GITLAB_REPO_URL: $(GITLAB_REPO_URL) 12 | GITLAB_GIT_USERNAME: $(GITLAB_GIT_USERNAME) 13 | GITLAB_GIT_PASSWORD: $(GITLAB_GIT_PASSWORD) 14 | AWS_REPO_URL: $(AWS_REPO_URL) 15 | AWS_GIT_USERNAME: $(AWS_GIT_USERNAME) 16 | AWS_GIT_PASSWORD: $(AWS_GIT_PASSWORD) 17 | 18 | sync_repo: 19 | stage: Sync Repository 20 | script: 21 | # Install urlencode function to encode reserved characters in passwords 22 | - sudo apt-get install gridsite-clients 23 | # Create local mirror of Azure DevOps repository 24 | - git clone --mirror https://${GITLAB_GIT_USERNAME}:$(urlencode ${GITLAB_GIT_PASSWORD})@${GITLAB_REPO_URL} repo-mirror 25 | # Sync AWS CodeCommit repository 26 | - cd repo-mirror 27 | - git push --mirror https://${AWS_GIT_USERNAME}:$(urlencode ${AWS_GIT_PASSWORD})@${AWS_REPO_URL} 28 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # This is an example goreleaser.yaml file with some sane defaults. 2 | # Make sure to check the documentation at http://goreleaser.com 3 | before: 4 | hooks: 5 | # You may remove this if you don't use go modules. 6 | - go mod download 7 | 8 | builds: 9 | - id: "go-bookstore" 10 | main: ./src/main.go 11 | env: 12 | # - GOOS=linux 13 | # - GOARCH=amd64 14 | - CGO_ENABLED=0 15 | goos: 16 | - linux 17 | - windows 18 | - darwin 19 | 20 | archives: 21 | - id: "archive-go-bookstore" 22 | name_template: "authc_{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" 23 | builds: # Builds reference which build instances should be archived in this archive. 24 | - "goapp" 25 | replacements: 26 | darwin: Darwin 27 | linux: Linux 28 | windows: Windows 29 | 386: i386 30 | amd64: x86_64 31 | allow_different_binary_count: true 32 | 33 | checksum: 34 | name_template: 'checksums.txt' 35 | 36 | snapshot: 37 | name_template: "{{ .Tag }}-next" 38 | 39 | changelog: 40 | sort: asc 41 | filters: 42 | exclude: 43 | - '^docs:' 44 | - '^test:' -------------------------------------------------------------------------------- /.jenkins/cicd-jenkins.jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | 4 | environment { 5 | AWS_REGION = 'ap-southeast-1' 6 | AWS_ACCOUNT_ID = '0987612345' 7 | IMAGE_NAME = 'devopscorner/bookstore' 8 | } 9 | 10 | stages { 11 | stage('Build') { 12 | steps { 13 | sh 'go build -o app' 14 | script { 15 | if (env.BRANCH_NAME ==~ /features\/.*/) { 16 | def semver = "1.0.0-${env.BRANCH_NAME.replace('features/', '')}.${env.GIT_COMMIT}" 17 | echo "Semantic version: ${semver}" 18 | sh "echo imageTag=${semver} >> variables.env" 19 | } else if (env.BRANCH_NAME ==~ /bugfix\/.*/) { 20 | def semver = "1.1.0-${env.BRANCH_NAME.replace('bugfix/', '')}.${env.GIT_COMMIT}" 21 | echo "Semantic version: ${semver}" 22 | sh "echo imageTag=${semver} >> variables.env" 23 | } else if (env.BRANCH_NAME ==~ /hotfix\/.*/) { 24 | def semver = "1.1.1-${env.BRANCH_NAME.replace('hotfix/', '')}.${env.GIT_COMMIT}" 25 | echo "Semantic version: ${semver}" 26 | sh "echo imageTag=${semver} >> variables.env" 27 | } else if (env.BRANCH_NAME == 'main') { 28 | def semver = "1.0.0-${env.GIT_COMMIT}" 29 | echo "Semantic version: ${semver}" 30 | sh "echo imageTag=${semver} >> variables.env" 31 | } 32 | } 33 | withCredentials([usernamePassword(credentialsId: 'aws-ecr-login', usernameVariable: 'AWS_ACCESS_KEY_ID', passwordVariable: 'AWS_SECRET_ACCESS_KEY')]) { 34 | sh "aws ecr get-login-password --region ${env.AWS_REGION} | docker login --username AWS --password-stdin ${env.AWS_ACCOUNT_ID}.dkr.ecr.${env.AWS_REGION}.amazonaws.com" 35 | sh "docker build -t ${env.AWS_ACCOUNT_ID}.dkr.ecr.${env.AWS_REGION}.amazonaws.com/${env.IMAGE_NAME}:${imageTag} ." 36 | sh "docker push ${env.AWS_ACCOUNT_ID}.dkr.ecr.${env.AWS_REGION}.amazonaws.com/${env.IMAGE_NAME}:${imageTag}" 37 | sh "docker tag ${env.AWS_ACCOUNT_ID}.dkr.ecr.${env.AWS_REGION}.amazonaws.com/${env.IMAGE_NAME}:${imageTag} ${env.AWS_ACCOUNT_ID}.dkr.ecr.${env.AWS_REGION}.amazonaws.com/${env.IMAGE_NAME}:latest" 38 | sh "docker push ${env.AWS_ACCOUNT_ID}.dkr.ecr.${env.AWS_REGION}.amazonaws.com/${env.IMAGE_NAME}:latest" 39 | } 40 | stash includes: 'app', name: 'app' 41 | } 42 | } 43 | stage('Deploy') { 44 | steps { 45 | unstash 'app' 46 | withAWS(region: env.AWS_REGION, roleAccount: "${env.AWS_ACCOUNT_ID}") { 47 | sh "curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash" 48 | sh "helmfile sync" 49 | } 50 | } 51 | environment { 52 | DEPLOYMENT_ENVIRONMENT_URL = "http://your-deployment-environment-url" 53 | } 54 | post { 55 | success { 56 | script { 57 | currentBuild.description = "Deployment completed successfully. Visit ${env.DEPLOYMENT_ENVIRONMENT_URL} for details." 58 | } 59 | } 60 | failure { 61 | script { 62 | currentBuild.description = "Deployment failed. Visit ${env.DEPLOYMENT_ENVIRONMENT_URL} for details." 63 | } 64 | } 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /.jenkins/jenkins-cicd.jenkinsfile: -------------------------------------------------------------------------------- 1 | def PATH_KUBECONFIG = '/home/ubuntu/.kube/config' 2 | def ECR_REPO = '0987612345.dkr.ecr.ap-southeast-1.amazonaws.com/devopscorner/bookstore' 3 | def VCS_REPO = 'git@github.com:devopscorner/golang-deployment.git' 4 | 5 | node { 6 | try { 7 | stage('Init'){ 8 | def dockerHome = tool 'docker' 9 | env.PATH = "${dockerHome}/bin:${env.PATH}" 10 | } 11 | stage('Clone'){ 12 | checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'CloneOption', depth: 2, noTags: true, reference: '', shallow: true]], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'devopscorner-git', url: GIT_REPO]]]) 13 | commitID = sh(returnStdout: true, script: ''' 14 | git log -n 1 --pretty=format:'%h' 15 | ''').trim() 16 | } 17 | script{ 18 | stage('Do build, push, & clean docker'){ 19 | 20 | sh 'docker build -f Dockerfile -t ' + ECR_REPO + ' .' 21 | 22 | withDockerRegistry(credentialsId: 'ecr:ap-southeast-1:devops', toolName: 'docker', url: 'https://' + ECR_REPO) { 23 | docker.image(ECR_REPO).push('latest') 24 | } 25 | withDockerRegistry(credentialsId: 'ecr:ap-southeast-1:devops', toolName: 'docker', url: 'https://'+ECR_REPO) { 26 | docker.image(ECR_REPO).push('dev-' + commitID) 27 | } 28 | 29 | sh 'sed -i "s/tag:.*$/tag: \"dev-' + commitID + '\"/" _infra/dev/helm-value.yaml' 30 | 31 | } 32 | } 33 | 34 | stage('Helmfile Deploy'){ 35 | withEnv(["KUBECONFIG=$PATH_KUBECONFIG","PATH=$PATH"]) { 36 | dir("_infra/dev"){ 37 | sh ''' 38 | kubectl config use-context arn:aws:eks:ap-southeast-1:0987612345:cluster/devopscorner-lab 39 | helmfile --version 40 | helmfile -f helmfile.yaml apply 41 | ''' 42 | } 43 | } 44 | } 45 | 46 | } catch (err) { 47 | echo 'Error: ' + err.toString() 48 | cleanWs() 49 | } 50 | 51 | stage('Cleaning up workspace') { 52 | cleanWs() 53 | } 54 | 55 | stage('Cleaning docker images'){ 56 | catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE'){ 57 | sh ''' 58 | docker rmi -f $(sudo docker images | grep ''' + ECR_REPO + ''' | awk '{print $3}') 59 | docker rmi -f $(sudo docker images --filter="dangling=true" -q --no-trunc) 60 | ''' 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /.openshift/cicd-openshift.yml: -------------------------------------------------------------------------------- 1 | apiVersion: build.openshift.io/v1 2 | kind: BuildConfig 3 | metadata: 4 | name: bookstore-build 5 | labels: 6 | app: bookstore 7 | spec: 8 | source: 9 | git: 10 | uri: https://github.com/devopscorner/golang-deployment.git 11 | contextDir: app 12 | strategy: 13 | dockerStrategy: 14 | dockerfilePath: Dockerfile 15 | type: Docker 16 | output: 17 | to: 18 | kind: ImageStreamTag 19 | name: devopscorner/bookstore:${IS_TAG} 20 | triggers: 21 | - type: ConfigChange 22 | - type: GitHub 23 | github: 24 | secret: my-secret 25 | - type: Generic 26 | generic: 27 | secret: my-secret 28 | - type: ImageChange 29 | imageChange: 30 | from: 31 | kind: ImageStreamTag 32 | name: devopscorner/bookstore:latest 33 | resources: 34 | limits: 35 | cpu: 1 36 | memory: 1Gi 37 | requests: 38 | cpu: 500m 39 | memory: 500Mi 40 | 41 | --- 42 | 43 | apiVersion: apps/v1 44 | kind: Deployment 45 | metadata: 46 | name: bookstore-deployment 47 | labels: 48 | app: bookstore 49 | spec: 50 | replicas: 1 51 | selector: 52 | matchLabels: 53 | app: bookstore 54 | template: 55 | metadata: 56 | labels: 57 | app: bookstore 58 | spec: 59 | containers: 60 | - name: bookstore 61 | image: devopscorner/bookstore:${IS_TAG} 62 | imagePullPolicy: Always 63 | ports: 64 | - containerPort: 8080 65 | imagePullSecrets: 66 | - name: my-registry-credentials 67 | 68 | --- 69 | 70 | apiVersion: v1 71 | kind: Service 72 | metadata: 73 | name: bookstore-service 74 | labels: 75 | app: bookstore 76 | spec: 77 | ports: 78 | - name: http 79 | port: 80 80 | targetPort: 8080 81 | selector: 82 | app: bookstore 83 | type: ClusterIP 84 | -------------------------------------------------------------------------------- /.semaphoreci/cicd-semaphoreci.yml: -------------------------------------------------------------------------------- 1 | version: v1.0 2 | 3 | agent: 4 | machine: 5 | type: e1-standard-2 6 | 7 | blocks: 8 | - name: Build and Deploy 9 | task: 10 | jobs: 11 | - name: Build and Push Docker Image 12 | commands: 13 | - checkout 14 | - sem-version set 15 | - if [[ "$SEMAPHORE_BRANCH_NAME" == "main" ]]; then 16 | semver=1.0.0-${SEMAPHORE_GIT_SHA:0:8} 17 | elif [[ "$SEMAPHORE_BRANCH_NAME" == "features/"* ]]; then 18 | semver=1.0.0-${SEMAPHORE_BRANCH_NAME#features/}.${SEMAPHORE_GIT_SHA:0:8} 19 | elif [[ "$SEMAPHORE_BRANCH_NAME" == "bugfix/"* ]]; then 20 | semver=1.1.0-${SEMAPHORE_BRANCH_NAME#bugfix/}.${SEMAPHORE_GIT_SHA:0:8} 21 | elif [[ "$SEMAPHORE_BRANCH_NAME" == "hotfix/"* ]]; then 22 | semver=1.1.1-${SEMAPHORE_BRANCH_NAME#hotfix/}.${SEMAPHORE_GIT_SHA:0:8} 23 | fi 24 | - echo "Semantic version: $semver" 25 | - echo "imageTag=$semver" >> $SEMAPHORE_WORKSPACE/variables.env 26 | - sem-service aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com 27 | - docker build -t $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver . 28 | - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver 29 | - docker tag $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:latest 30 | - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:latest 31 | - name: Deploy to Kubernetes using Helmfile 32 | commands: 33 | - curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash 34 | - helmfile sync 35 | environment: 36 | AWS_REGION: ap-southeast-1 37 | AWS_ACCOUNT_ID: 0987612345 38 | IMAGE_NAME: devopscorner/bookstore 39 | -------------------------------------------------------------------------------- /.spinnaker/cicd-spinnaker.yml: -------------------------------------------------------------------------------- 1 | pipelines: 2 | - application: bookstore 3 | name: bookstore-pipeline 4 | triggers: 5 | - type: git 6 | branch: main 7 | repo: https://github.com/devopscorner/golang-deployment.git 8 | secretName: git-secret 9 | stages: 10 | - name: build 11 | type: docker 12 | inputArtifacts: 13 | - name: source 14 | account: github-account 15 | id: '{{build.id}}' 16 | docker: 17 | dockerfile: Dockerfile 18 | account: dockerhub-account 19 | tags: 20 | - ${TRAVIS_COMMIT:0:8} 21 | - name: deploy 22 | type: deployManifest 23 | manifestArtifactAccount: kubernetes-account 24 | manifestArtifactId: '{{build.id}}' 25 | account: kubernetes-account 26 | cloudProvider: kubernetes 27 | source: 28 | account: github-account 29 | manifestName: bookstore.yml 30 | trafficManagement: 31 | options: 32 | enableTraffic: true 33 | -------------------------------------------------------------------------------- /.travisci/cicd-travisci.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | services: 4 | - docker 5 | 6 | env: 7 | global: 8 | - AWS_REGION=ap-southeast-1 9 | - AWS_ACCOUNT_ID=0987612345 10 | - IMAGE_NAME=devopscorner/bookstore 11 | 12 | before_script: 13 | - curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 14 | - dep ensure 15 | 16 | script: 17 | - if [[ "$TRAVIS_BRANCH" == "main" ]]; then 18 | semver=1.0.0-${TRAVIS_COMMIT:0:8}; 19 | elif [[ "$TRAVIS_BRANCH" == "features/"* ]]; then 20 | semver=1.0.0-${TRAVIS_BRANCH#features/}.${TRAVIS_COMMIT:0:8}; 21 | elif [[ "$TRAVIS_BRANCH" == "bugfix/"* ]]; then 22 | semver=1.1.0-${TRAVIS_BRANCH#bugfix/}.${TRAVIS_COMMIT:0:8}; 23 | elif [[ "$TRAVIS_BRANCH" == "hotfix/"* ]]; then 24 | semver=1.1.1-${TRAVIS_BRANCH#hotfix/}.${TRAVIS_COMMIT:0:8}; 25 | fi 26 | - echo "Semantic version: $semver" 27 | - echo "imageTag=$semver" >> $HOME/variables.env 28 | - docker build -t $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver . 29 | - docker tag $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:latest 30 | - aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com 31 | - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver 32 | - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:latest 33 | 34 | after_success: 35 | - curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash 36 | - helmfile sync 37 | 38 | branches: 39 | only: 40 | - main 41 | - /^features\/.*$/ 42 | - /^bugfix\/.*$/ 43 | - /^hotfix\/.*$/ 44 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ### Builder ### 2 | FROM golang:1.21.4-alpine3.17 as builder 3 | 4 | WORKDIR /go/src/app 5 | ENV GIN_MODE=release 6 | ENV GOPATH=/go 7 | 8 | RUN apk add --no-cache \ 9 | build-base \ 10 | git \ 11 | curl \ 12 | make \ 13 | bash 14 | 15 | COPY src /go/src/app 16 | 17 | RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 \ 18 | cd /go/src/app && \ 19 | go build -mod=readonly -ldflags="-s -w" -o goapp 20 | 21 | ### Binary ### 22 | FROM golang:1.21.4-alpine3.17 23 | 24 | ARG BUILD_DATE 25 | ARG BUILD_VERSION 26 | ARG GIT_COMMIT 27 | ARG GIT_URL 28 | 29 | ENV VENDOR="DevOpsCornerId" 30 | ENV AUTHOR="DevOpsCorner.id " 31 | ENV IMG_NAME="alpine" 32 | ENV IMG_VERSION="3.17" 33 | ENV IMG_DESC="Docker GO App Alpine 3.17" 34 | ENV IMG_ARCH="amd64/x86_64" 35 | 36 | ENV ALPINE_VERSION="3.17" 37 | ENV GIN_MODE=release 38 | ENV APP_URL=${APP_URL:-http://localhost} 39 | ENV APP_PORT=${APP_PORT:-8080} 40 | ENV DB_CONNECTION=${DB_CONNECTION:-sqlite} 41 | ENV DB_REGION=${DB_REGION:-ap-southeast-1} 42 | ENV DB_HOST=${DB_HOST:-localhost} 43 | ENV DB_PORT=${DB_PORT} 44 | ENV DB_DATABASE=${DB_DATABASE:-go-bookstore.db} 45 | ENV DB_USERNAME=${DB_USERNAME:-root} 46 | ENV DB_PASSWORD=${DB_PASSWORD} 47 | ENV JWT_AUTH_USERNAME=${JWT_AUTH_USERNAME:-devopscorner} 48 | ENV JWT_AUTH_PASSWORD=${JWT_AUTH_PASSWORD:-DevOpsCorner@2023} 49 | ENV JWT_SECRET=${JWT_SECRET:-s3cr3t} 50 | 51 | LABEL maintainer="$AUTHOR" \ 52 | architecture="$IMG_ARCH" \ 53 | ubuntu-version="$ALPINE_VERSION" \ 54 | org.label-schema.build-date="$BUILD_DATE" \ 55 | org.label-schema.name="$IMG_NAME" \ 56 | org.label-schema.description="$IMG_DESC" \ 57 | org.label-schema.vcs-ref="$GIT_COMMIT" \ 58 | org.label-schema.vcs-url="$GIT_URL" \ 59 | org.label-schema.vendor="$VENDOR" \ 60 | org.label-schema.version="$BUILD_VERSION" \ 61 | org.label-schema.schema-version="$IMG_VERSION" \ 62 | org.opencontainers.image.authors="$AUTHOR" \ 63 | org.opencontainers.image.description="$IMG_DESC" \ 64 | org.opencontainers.image.vendor="$VENDOR" \ 65 | org.opencontainers.image.version="$IMG_VERSION" \ 66 | org.opencontainers.image.revision="$GIT_COMMIT" \ 67 | org.opencontainers.image.created="$BUILD_DATE" \ 68 | fr.hbis.docker.base.build-date="$BUILD_DATE" \ 69 | fr.hbis.docker.base.name="$IMG_NAME" \ 70 | fr.hbis.docker.base.vendor="$VENDOR" \ 71 | fr.hbis.docker.base.version="$BUILD_VERSION" 72 | 73 | WORKDIR /go 74 | 75 | COPY --from=builder /go/src/app/goapp /go/goapp 76 | COPY src /go/src 77 | COPY src/.env.example /go/.env 78 | COPY entrypoint.sh /go/entrypoint 79 | 80 | ENTRYPOINT ["/go/goapp"] 81 | EXPOSE 80 443 8080 82 | -------------------------------------------------------------------------------- /Dockerfile.alpine-3.15: -------------------------------------------------------------------------------- 1 | ### Builder ### 2 | FROM golang:1.19.3-alpine3.15 as builder 3 | 4 | WORKDIR /go/src/app 5 | ENV GIN_MODE=release 6 | ENV GOPATH=/go 7 | 8 | RUN apk add --no-cache \ 9 | build-base \ 10 | git \ 11 | curl \ 12 | make \ 13 | bash 14 | 15 | COPY src /go/src/app 16 | 17 | RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 \ 18 | cd /go/src/app && \ 19 | go build -mod=readonly -ldflags="-s -w" -o goapp 20 | 21 | ### Binary ### 22 | FROM golang:1.19.3-alpine3.15 23 | 24 | ARG BUILD_DATE 25 | ARG BUILD_VERSION 26 | ARG GIT_COMMIT 27 | ARG GIT_URL 28 | 29 | ENV VENDOR="DevOpsCornerId" 30 | ENV AUTHOR="DevOpsCorner.id " 31 | ENV IMG_NAME="alpine" 32 | ENV IMG_VERSION="3.15" 33 | ENV IMG_DESC="Docker GO App Alpine 3.15" 34 | ENV IMG_ARCH="amd64/x86_64" 35 | 36 | ENV ALPINE_VERSION="3.15" 37 | ENV GIN_MODE=release 38 | ENV APP_URL=${APP_URL:-http://localhost} 39 | ENV APP_PORT=${APP_PORT:-8080} 40 | ENV DB_CONNECTION=${DB_CONNECTION:-sqlite} 41 | ENV DB_REGION=${DB_REGION:-ap-southeast-1} 42 | ENV DB_HOST=${DB_HOST:-localhost} 43 | ENV DB_PORT=${DB_PORT} 44 | ENV DB_DATABASE=${DB_DATABASE:-go-bookstore.db} 45 | ENV DB_USERNAME=${DB_USERNAME:-root} 46 | ENV DB_PASSWORD=${DB_PASSWORD} 47 | ENV JWT_AUTH_USERNAME=${JWT_AUTH_USERNAME:-devopscorner} 48 | ENV JWT_AUTH_PASSWORD=${JWT_AUTH_PASSWORD:-DevOpsCorner@2023} 49 | ENV JWT_SECRET=${JWT_SECRET:-s3cr3t} 50 | 51 | LABEL maintainer="$AUTHOR" \ 52 | architecture="$IMG_ARCH" \ 53 | ubuntu-version="$ALPINE_VERSION" \ 54 | org.label-schema.build-date="$BUILD_DATE" \ 55 | org.label-schema.name="$IMG_NAME" \ 56 | org.label-schema.description="$IMG_DESC" \ 57 | org.label-schema.vcs-ref="$GIT_COMMIT" \ 58 | org.label-schema.vcs-url="$GIT_URL" \ 59 | org.label-schema.vendor="$VENDOR" \ 60 | org.label-schema.version="$BUILD_VERSION" \ 61 | org.label-schema.schema-version="$IMG_VERSION" \ 62 | org.opencontainers.image.authors="$AUTHOR" \ 63 | org.opencontainers.image.description="$IMG_DESC" \ 64 | org.opencontainers.image.vendor="$VENDOR" \ 65 | org.opencontainers.image.version="$IMG_VERSION" \ 66 | org.opencontainers.image.revision="$GIT_COMMIT" \ 67 | org.opencontainers.image.created="$BUILD_DATE" \ 68 | fr.hbis.docker.base.build-date="$BUILD_DATE" \ 69 | fr.hbis.docker.base.name="$IMG_NAME" \ 70 | fr.hbis.docker.base.vendor="$VENDOR" \ 71 | fr.hbis.docker.base.version="$BUILD_VERSION" 72 | 73 | WORKDIR /go 74 | 75 | COPY --from=builder /go/src/app/goapp /go/goapp 76 | COPY src /go/src 77 | COPY src/.env.example /go/.env 78 | COPY entrypoint.sh /go/entrypoint 79 | 80 | ENTRYPOINT ["/go/goapp"] 81 | EXPOSE 80 443 8080 82 | -------------------------------------------------------------------------------- /Dockerfile.alpine-3.16: -------------------------------------------------------------------------------- 1 | ### Builder ### 2 | FROM golang:1.19-alpine3.16 as builder 3 | 4 | WORKDIR /go/src/app 5 | ENV GIN_MODE=release 6 | ENV GOPATH=/go 7 | 8 | RUN apk add --no-cache \ 9 | build-base \ 10 | git \ 11 | curl \ 12 | make \ 13 | bash 14 | 15 | COPY src /go/src/app 16 | 17 | RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 \ 18 | cd /go/src/app && \ 19 | go build -mod=readonly -ldflags="-s -w" -o goapp 20 | 21 | ### Binary ### 22 | FROM golang:1.19-alpine3.16 23 | 24 | ARG BUILD_DATE 25 | ARG BUILD_VERSION 26 | ARG GIT_COMMIT 27 | ARG GIT_URL 28 | 29 | ENV VENDOR="DevOpsCornerId" 30 | ENV AUTHOR="DevOpsCorner.id " 31 | ENV IMG_NAME="alpine" 32 | ENV IMG_VERSION="3.16" 33 | ENV IMG_DESC="Docker GO App Alpine 3.16" 34 | ENV IMG_ARCH="amd64/x86_64" 35 | 36 | ENV ALPINE_VERSION="3.16" 37 | ENV GIN_MODE=release 38 | ENV APP_URL=${APP_URL:-http://localhost} 39 | ENV APP_PORT=${APP_PORT:-8080} 40 | ENV DB_CONNECTION=${DB_CONNECTION:-sqlite} 41 | ENV DB_REGION=${DB_REGION:-ap-southeast-1} 42 | ENV DB_HOST=${DB_HOST:-localhost} 43 | ENV DB_PORT=${DB_PORT} 44 | ENV DB_DATABASE=${DB_DATABASE:-go-bookstore.db} 45 | ENV DB_USERNAME=${DB_USERNAME:-root} 46 | ENV DB_PASSWORD=${DB_PASSWORD} 47 | ENV JWT_AUTH_USERNAME=${JWT_AUTH_USERNAME:-devopscorner} 48 | ENV JWT_AUTH_PASSWORD=${JWT_AUTH_PASSWORD:-DevOpsCorner@2023} 49 | ENV JWT_SECRET=${JWT_SECRET:-s3cr3t} 50 | 51 | LABEL maintainer="$AUTHOR" \ 52 | architecture="$IMG_ARCH" \ 53 | ubuntu-version="$ALPINE_VERSION" \ 54 | org.label-schema.build-date="$BUILD_DATE" \ 55 | org.label-schema.name="$IMG_NAME" \ 56 | org.label-schema.description="$IMG_DESC" \ 57 | org.label-schema.vcs-ref="$GIT_COMMIT" \ 58 | org.label-schema.vcs-url="$GIT_URL" \ 59 | org.label-schema.vendor="$VENDOR" \ 60 | org.label-schema.version="$BUILD_VERSION" \ 61 | org.label-schema.schema-version="$IMG_VERSION" \ 62 | org.opencontainers.image.authors="$AUTHOR" \ 63 | org.opencontainers.image.description="$IMG_DESC" \ 64 | org.opencontainers.image.vendor="$VENDOR" \ 65 | org.opencontainers.image.version="$IMG_VERSION" \ 66 | org.opencontainers.image.revision="$GIT_COMMIT" \ 67 | org.opencontainers.image.created="$BUILD_DATE" \ 68 | fr.hbis.docker.base.build-date="$BUILD_DATE" \ 69 | fr.hbis.docker.base.name="$IMG_NAME" \ 70 | fr.hbis.docker.base.vendor="$VENDOR" \ 71 | fr.hbis.docker.base.version="$BUILD_VERSION" 72 | 73 | WORKDIR /go 74 | 75 | COPY --from=builder /go/src/app/goapp /go/goapp 76 | COPY src /go/src 77 | COPY src/.env.example /go/.env 78 | COPY entrypoint.sh /go/entrypoint 79 | 80 | ENTRYPOINT ["/go/goapp"] 81 | EXPOSE 80 443 8080 82 | -------------------------------------------------------------------------------- /Dockerfile.alpine-3.17: -------------------------------------------------------------------------------- 1 | ### Builder ### 2 | FROM golang:1-alpine3.17 as builder 3 | 4 | WORKDIR /go/src/app 5 | ENV GIN_MODE=release 6 | ENV GOPATH=/go 7 | 8 | RUN apk add --no-cache \ 9 | build-base \ 10 | git \ 11 | curl \ 12 | make \ 13 | bash 14 | 15 | COPY src /go/src/app 16 | 17 | RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 \ 18 | cd /go/src/app && \ 19 | go build -mod=readonly -ldflags="-s -w" -o goapp 20 | 21 | ### Binary ### 22 | FROM golang:1-alpine3.17 23 | 24 | ARG BUILD_DATE 25 | ARG BUILD_VERSION 26 | ARG GIT_COMMIT 27 | ARG GIT_URL 28 | 29 | ENV VENDOR="DevOpsCornerId" 30 | ENV AUTHOR="DevOpsCorner.id " 31 | ENV IMG_NAME="alpine" 32 | ENV IMG_VERSION="3.17" 33 | ENV IMG_DESC="Docker GO App Alpine 3.17" 34 | ENV IMG_ARCH="amd64/x86_64" 35 | 36 | ENV ALPINE_VERSION="3.17" 37 | ENV GIN_MODE=release 38 | ENV APP_URL=${APP_URL:-http://localhost} 39 | ENV APP_PORT=${APP_PORT:-8080} 40 | ENV DB_CONNECTION=${DB_CONNECTION:-sqlite} 41 | ENV DB_REGION=${DB_REGION:-ap-southeast-1} 42 | ENV DB_HOST=${DB_HOST:-localhost} 43 | ENV DB_PORT=${DB_PORT} 44 | ENV DB_DATABASE=${DB_DATABASE:-go-bookstore.db} 45 | ENV DB_USERNAME=${DB_USERNAME:-root} 46 | ENV DB_PASSWORD=${DB_PASSWORD} 47 | ENV JWT_AUTH_USERNAME=${JWT_AUTH_USERNAME:-devopscorner} 48 | ENV JWT_AUTH_PASSWORD=${JWT_AUTH_PASSWORD:-DevOpsCorner@2023} 49 | ENV JWT_SECRET=${JWT_SECRET:-s3cr3t} 50 | 51 | LABEL maintainer="$AUTHOR" \ 52 | architecture="$IMG_ARCH" \ 53 | ubuntu-version="$ALPINE_VERSION" \ 54 | org.label-schema.build-date="$BUILD_DATE" \ 55 | org.label-schema.name="$IMG_NAME" \ 56 | org.label-schema.description="$IMG_DESC" \ 57 | org.label-schema.vcs-ref="$GIT_COMMIT" \ 58 | org.label-schema.vcs-url="$GIT_URL" \ 59 | org.label-schema.vendor="$VENDOR" \ 60 | org.label-schema.version="$BUILD_VERSION" \ 61 | org.label-schema.schema-version="$IMG_VERSION" \ 62 | org.opencontainers.image.authors="$AUTHOR" \ 63 | org.opencontainers.image.description="$IMG_DESC" \ 64 | org.opencontainers.image.vendor="$VENDOR" \ 65 | org.opencontainers.image.version="$IMG_VERSION" \ 66 | org.opencontainers.image.revision="$GIT_COMMIT" \ 67 | org.opencontainers.image.created="$BUILD_DATE" \ 68 | fr.hbis.docker.base.build-date="$BUILD_DATE" \ 69 | fr.hbis.docker.base.name="$IMG_NAME" \ 70 | fr.hbis.docker.base.vendor="$VENDOR" \ 71 | fr.hbis.docker.base.version="$BUILD_VERSION" 72 | 73 | WORKDIR /go 74 | 75 | COPY --from=builder /go/src/app/goapp /go/goapp 76 | COPY src /go/src 77 | COPY src/.env.example /go/.env 78 | COPY entrypoint.sh /go/entrypoint 79 | 80 | ENTRYPOINT ["/go/goapp"] 81 | EXPOSE 80 443 8080 82 | -------------------------------------------------------------------------------- /_infra/helm/dev/api-grpc-values.yml: -------------------------------------------------------------------------------- 1 | replicaCount: 1 2 | 3 | secret: 4 | enabled: false 5 | 6 | configMap: 7 | enabled: true 8 | name: "bookstore-grpc-api" 9 | mountPath: /app/core/config 10 | readOnly: true 11 | data: 12 | .app.config.json : |- 13 | { 14 | "AppName": "GO App", 15 | "GRPCTimeout": 10, 16 | "CacheExpiry": 300, 17 | "CacheCleanup": 600, 18 | "DefaultPageLimit": 3, 19 | "ClientTimeout": 10 20 | } 21 | .env : |- 22 | export GIN_MODE=release 23 | 24 | image: 25 | repository: YOUR_AWS_ACCOUNT.dkr.ecr.ap-southeast-1.amazonaws.com/devopscorner/bookstore-grpc 26 | pullPolicy: Always 27 | tag: "latest" 28 | 29 | imagePullSecrets: [] 30 | nameOverride: "" 31 | fullnameOverride: "bookstore-grpc" 32 | 33 | serviceAccount: 34 | create: true 35 | annotations: {} 36 | name: bookstore-grpc 37 | namespace: devops-tools 38 | 39 | service: 40 | type: ClusterIP 41 | ports: 42 | - name: http 43 | port: 80 44 | targetPort: 8085 45 | protocol: TCP 46 | 47 | container: 48 | ports: 49 | - name: http 50 | containerPort: 8085 51 | protocol: TCP 52 | 53 | ingress: 54 | enabled: true 55 | annotations: 56 | nginx.ingress.kubernetes.io/cors-allow-headers: '*' 57 | nginx.ingress.kubernetes.io/cors-allow-methods: '*' 58 | nginx.ingress.kubernetes.io/cors-allow-origin: '*' 59 | nginx.ingress.kubernetes.io/enable-cors: "true" 60 | nginx.ingress.kubernetes.io/affinity: cookie 61 | nginx.ingress.kubernetes.io/from-to-www-redirect: "true" 62 | kubernetes.io/ingress.class: nginx 63 | ingress.kubernetes.io/whitelist-source-range: 32.0.0.0/32 64 | hosts: 65 | #- host: bookstore-grpc.awscb.id 66 | - host: bookstore-grpc.devops-tools.svc.cluster.local 67 | http: 68 | paths: 69 | - path: / 70 | backend: 71 | serviceName: bookstore-grpc 72 | servicePort: 80 73 | tls: [] 74 | 75 | 76 | application: 77 | enabled: true 78 | env: 79 | - name: HELM_TEMPLATE_NAME 80 | value: api 81 | 82 | resources: 83 | limits: 84 | cpu: 200m 85 | memory: 300Mi 86 | requests: 87 | cpu: 100m 88 | memory: 150Mi 89 | 90 | autoscaling: 91 | enabled: true 92 | minReplicas: 1 93 | maxReplicas: 4 94 | targetCPUUtilizationPercentage: 80 95 | targetMemoryUtilizationPercentage: 80 96 | 97 | nodeSelector: 98 | enabled: true 99 | select: 100 | node: "devops-tools" 101 | 102 | tolerations: [] 103 | 104 | affinity: {} 105 | 106 | podAnnotations: {} 107 | 108 | podSecurityContext: {} 109 | 110 | securityContext: {} 111 | -------------------------------------------------------------------------------- /_infra/helm/dev/api-rest-values.yml: -------------------------------------------------------------------------------- 1 | replicaCount: 1 2 | 3 | secret: 4 | enabled: false 5 | 6 | configMap: 7 | enabled: true 8 | name: "bookstore-rest-api" 9 | mountPath: /app/core/config 10 | readOnly: true 11 | data: 12 | .app.config.json : |- 13 | { 14 | "AppName": "GO App", 15 | "GRPCTimeout": 10, 16 | "CacheExpiry": 300, 17 | "CacheCleanup": 600, 18 | "DefaultPageLimit": 3, 19 | "ClientTimeout": 10 20 | } 21 | .env : |- 22 | export GIN_MODE=release 23 | 24 | image: 25 | repository: YOUR_AWS_ACCOUNT.dkr.ecr.ap-southeast-1.amazonaws.com/devopscorner/bookstore 26 | pullPolicy: Always 27 | tag: "latest" 28 | 29 | imagePullSecrets: [] 30 | nameOverride: "" 31 | fullnameOverride: "bookstore-rest" 32 | 33 | serviceAccount: 34 | create: true 35 | annotations: {} 36 | name: bookstore-rest 37 | namespace: devops-tools 38 | 39 | service: 40 | type: ClusterIP 41 | ports: 42 | - name: http 43 | port: 80 44 | targetPort: 8080 45 | protocol: TCP 46 | 47 | container: 48 | ports: 49 | - name: http 50 | containerPort: 8080 51 | protocol: TCP 52 | 53 | ingress: 54 | enabled: true 55 | annotations: 56 | nginx.ingress.kubernetes.io/cors-allow-headers: '*' 57 | nginx.ingress.kubernetes.io/cors-allow-methods: '*' 58 | nginx.ingress.kubernetes.io/cors-allow-origin: '*' 59 | nginx.ingress.kubernetes.io/enable-cors: "true" 60 | nginx.ingress.kubernetes.io/affinity: cookie 61 | nginx.ingress.kubernetes.io/from-to-www-redirect: "true" 62 | kubernetes.io/ingress.class: nginx 63 | ingress.kubernetes.io/whitelist-source-range: 32.0.0.0/32 64 | hosts: 65 | #- host: bookstore-rest.awscb.id 66 | - host: bookstore-rest.devops-tools.svc.cluster.local 67 | http: 68 | paths: 69 | - path: / 70 | backend: 71 | serviceName: bookstore-rest 72 | servicePort: 80 73 | tls: [] 74 | 75 | 76 | application: 77 | enabled: true 78 | env: 79 | - name: HELM_TEMPLATE_NAME 80 | value: api 81 | 82 | resources: 83 | limits: 84 | cpu: 200m 85 | memory: 300Mi 86 | requests: 87 | cpu: 100m 88 | memory: 150Mi 89 | 90 | autoscaling: 91 | enabled: true 92 | minReplicas: 1 93 | maxReplicas: 4 94 | targetCPUUtilizationPercentage: 80 95 | targetMemoryUtilizationPercentage: 80 96 | 97 | nodeSelector: 98 | enabled: true 99 | select: 100 | node: "devops-tools" 101 | 102 | tolerations: [] 103 | 104 | affinity: {} 105 | 106 | podAnnotations: {} 107 | 108 | podSecurityContext: {} 109 | 110 | securityContext: {} 111 | -------------------------------------------------------------------------------- /_infra/helm/dev/helm-template.yml: -------------------------------------------------------------------------------- 1 | --- 2 | repositories: 3 | - name: devopscorner-lab 4 | url: s3://devopscorner-helm-chart/lab 5 | 6 | templates: 7 | default: &default 8 | namespace: devops-tools 9 | version: "1.4.0-rc" 10 | 11 | releases: 12 | - name: bookstore-rest 13 | chart: devopscorner-lab/api 14 | values: 15 | - ./api-rest-values.yml 16 | <<: *default 17 | 18 | - name: bookstore-grpc 19 | chart: devopscorner-lab/api 20 | values: 21 | - ./api-grpc-values.yml 22 | <<: *default -------------------------------------------------------------------------------- /_infra/iam/ecr-codebuild-permission.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "CodeBuildAccessPrincipal", 6 | "Effect": "Allow", 7 | "Principal": { 8 | "Service": "codebuild.amazonaws.com" 9 | }, 10 | "Action": [ 11 | "ecr:GetDownloadUrlForLayer", 12 | "ecr:BatchGetImage", 13 | "ecr:BatchCheckLayerAvailability" 14 | ], 15 | "Condition": { 16 | "StringEquals": { 17 | "aws:SourceArn": "arn:aws:codebuild:ap-southeast-1:YOUR_AWS_ACCOUNT:project/*", 18 | "aws:SourceAccount": "YOUR_AWS_ACCOUNT" 19 | } 20 | } 21 | }, 22 | { 23 | "Sid": "CodeBuildAccessCrossAccount", 24 | "Effect": "Allow", 25 | "Principal": { 26 | "AWS": "arn:aws:iam::YOUR_AWS_ACCOUNT:root" 27 | }, 28 | "Action": [ 29 | "ecr:GetDownloadUrlForLayer", 30 | "ecr:BatchGetImage", 31 | "ecr:BatchCheckLayerAvailability" 32 | ] 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /_infra/iam/policy-cicd.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "AllowAccess", 6 | "Effect": "Allow", 7 | "Action": [ 8 | "codecommit:CancelUploadArchive", 9 | "codecommit:GetBranch", 10 | "codecommit:GetCommit", 11 | "codecommit:GetUploadArchiveStatus", 12 | "codecommit:UploadArchive", 13 | "codecommit:GetBranch", 14 | "codedeploy:CreateDeployment", 15 | "codedeploy:GetApplication", 16 | "codedeploy:GetApplicationRevision", 17 | "codedeploy:GetDeployment", 18 | "codedeploy:GetDeploymentConfig", 19 | "codedeploy:RegisterApplicationRevision", 20 | "ecr:GetAuthorizationToken", 21 | "ecr:BatchCheckLayerAvailability", 22 | "ecr:GetDownloadUrlForLayer", 23 | "ecr:GetRepositoryPolicy", 24 | "ecr:DescribeRepositories", 25 | "ecr:ListImages", 26 | "ecr:DescribeImages", 27 | "ecr:BatchGetImage", 28 | "ecr:GetLifecyclePolicy", 29 | "ecr:GetLifecyclePolicyPreview", 30 | "ecr:ListTagsForResource", 31 | "ecr:DescribeImageScanFindings", 32 | "lambda:InvokeFunction", 33 | "lambda:ListFunctions", 34 | "servicecatalog:ListProvisioningArtifacts", 35 | "servicecatalog:CreateProvisioningArtifact", 36 | "servicecatalog:DescribeProvisioningArtifact", 37 | "servicecatalog:DeleteProvisioningArtifact", 38 | "servicecatalog:UpdateProduct" 39 | ], 40 | "Resource": [ 41 | "arn:aws:ecr:ap-southeast-1:YOUR_AWS_ACCOUNT:repository/*" 42 | ] 43 | }, 44 | { 45 | "Effect": "Allow", 46 | "Action": "kms:*", 47 | "Resource": "*", 48 | "Condition": { 49 | "StringEquals": { 50 | "kms:KeySpec": "SYMMETRIC_DEFAULT" 51 | } 52 | } 53 | } 54 | ] 55 | } -------------------------------------------------------------------------------- /_infra/iam/policy-kms-GenerateDataKey.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": ["kms:GenerateDataKey"], 7 | "Resource": [ 8 | "arn:aws:kms:ap-southeast-1:YOUR_AWS_ACCOUNT:key/CMK_STAGING_HASH_ID", 9 | "arn:aws:kms:ap-southeast-1:YOUR_AWS_ACCOUNT:key/CMK_PROD_HASH_ID" 10 | ] 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /_infra/iam/policy-kms.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": "kms:*", 7 | "Resource": "*", 8 | "Condition": { 9 | "StringEquals": { 10 | "kms:KeySpec": "SYMMETRIC_DEFAULT" 11 | } 12 | } 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /clone.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 595, 3 | "uniques": 309, 4 | "clones": [ 5 | { 6 | "timestamp": "2025-05-24T00:00:00Z", 7 | "count": 7, 8 | "uniques": 7 9 | }, 10 | { 11 | "timestamp": "2025-05-25T00:00:00Z", 12 | "count": 42, 13 | "uniques": 34 14 | }, 15 | { 16 | "timestamp": "2025-05-26T00:00:00Z", 17 | "count": 43, 18 | "uniques": 34 19 | }, 20 | { 21 | "timestamp": "2025-05-27T00:00:00Z", 22 | "count": 54, 23 | "uniques": 32 24 | }, 25 | { 26 | "timestamp": "2025-05-28T00:00:00Z", 27 | "count": 41, 28 | "uniques": 31 29 | }, 30 | { 31 | "timestamp": "2025-05-29T00:00:00Z", 32 | "count": 41, 33 | "uniques": 34 34 | }, 35 | { 36 | "timestamp": "2025-05-30T00:00:00Z", 37 | "count": 40, 38 | "uniques": 33 39 | }, 40 | { 41 | "timestamp": "2025-05-31T00:00:00Z", 42 | "count": 43, 43 | "uniques": 34 44 | }, 45 | { 46 | "timestamp": "2025-06-01T00:00:00Z", 47 | "count": 50, 48 | "uniques": 38 49 | }, 50 | { 51 | "timestamp": "2025-06-02T00:00:00Z", 52 | "count": 44, 53 | "uniques": 33 54 | }, 55 | { 56 | "timestamp": "2025-06-03T00:00:00Z", 57 | "count": 42, 58 | "uniques": 33 59 | }, 60 | { 61 | "timestamp": "2025-06-04T00:00:00Z", 62 | "count": 46, 63 | "uniques": 38 64 | }, 65 | { 66 | "timestamp": "2025-06-05T00:00:00Z", 67 | "count": 43, 68 | "uniques": 33 69 | }, 70 | { 71 | "timestamp": "2025-06-06T00:00:00Z", 72 | "count": 40, 73 | "uniques": 33 74 | }, 75 | { 76 | "timestamp": "2025-06-07T00:00:00Z", 77 | "count": 19, 78 | "uniques": 17 79 | } 80 | ] 81 | } 82 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | #================================================================================================ 4 | # NETWORK SETUP 5 | #================================================================================================ 6 | networks: 7 | devopscorner_net: 8 | name: devopscorner_net 9 | driver: bridge 10 | ipam: 11 | config: 12 | - subnet: 172.148.0.0/16 13 | 14 | #================================================================================================ 15 | # VOLUME SETUP 16 | #================================================================================================ 17 | volumes: 18 | vol_portainer: 19 | driver: ${VOLUMES_DRIVER:-local} 20 | driver_opts: 21 | o: bind 22 | type: none 23 | device: ${DATA_PORTAINER:-/opt/data/docker/portainer2.9} 24 | vol_go_bookstore: 25 | driver: ${VOLUMES_DRIVER:-local} 26 | driver_opts: 27 | o: bind 28 | type: none 29 | device: ${DATA_GO_BOOKSTORE:-/opt/data/docker/go-bookstore} 30 | 31 | services: 32 | #================================================================================================ 33 | # PORTAINER 34 | #================================================================================================ 35 | portainer: 36 | image: dockerframework/portainer:${PORTAINER_VERSION:-2.9} 37 | container_name: ${CONTAINER_PORTAINER:-devopscorner_portainer} 38 | restart: unless-stopped 39 | ports: 40 | - "${PORT_PORTAINER:-5212}:9000" 41 | volumes: 42 | # - /etc/localtime:/etc/localtime:ro ## Do not use it in mac 43 | - /var/run/docker.sock:/var/run/docker.sock ## Do not use it in k8s 44 | - vol_portainer:/data 45 | environment: 46 | - PORTAINER_TEMPLATE=generic 47 | - PORTAINER_VERSION=${PORTAINER_VERSION:-2.9} 48 | privileged: true 49 | networks: 50 | devopscorner_net: 51 | ipv4_address: ${CONTAINER_IP_PORTAINER:-172.148.148.5} 52 | 53 | #================================================================================================ 54 | # GOLANG-BOOKSTORE 55 | #================================================================================================ 56 | go-bookstore: 57 | build: 58 | context: . 59 | dockerfile: Dockerfile 60 | container_name: ${CONTAINER_GO_BOOKSTORE:-devopscorner_go_bookstore} 61 | restart: unless-stopped 62 | ports: 63 | - "${PORT_GO_BOOKSTORE:-8080}:8080" 64 | volumes: 65 | # - /etc/localtime:/etc/localtime:ro ## Do not use it in mac 66 | - /var/run/docker.sock:/var/run/docker.sock ## Do not use it in k8s 67 | environment: 68 | - TZ="Asia/Jakarta" 69 | - ALPINE_VERSION=${ALPINE_VERSION:-3.17} 70 | - GIN_MODE=release 71 | - APP_URL=${APP_URL:-http://localhost} 72 | - APP_PORT=${APP_PORT:-8080} 73 | - DB_CONNECTION=${DB_CONNECTION:-sqlite} 74 | - DB_REGION=${DB_REGION:-ap-southeast-1} 75 | - DB_HOST=${DB_HOST:-localhost} 76 | - DB_PORT=${DB_PORT} 77 | - DB_DATABASE=${DB_DATABASE:-go-bookstore.db} 78 | - DB_USERNAME=${DB_USERNAME:-root} 79 | - DB_PASSWORD=${DB_PASSWORD} 80 | - JWT_AUTH_USERNAME=${JWT_AUTH_USERNAME:-devopscorner} 81 | - JWT_AUTH_PASSWORD=${JWT_AUTH_PASSWORD:-DevOpsCorner@2023} 82 | - JWT_SECRET=${JWT_SECRET:-s3cr3t} 83 | privileged: true 84 | tty: true 85 | networks: 86 | devopscorner_net: 87 | ipv4_address: ${CONTAINER_IP_GO_BOOKSTORE:-172.148.148.225} 88 | -------------------------------------------------------------------------------- /dockerhub-build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # ----------------------------------------------------------------------------- 3 | # Docker Build Container (DockerHub) 4 | # ----------------------------------------------------------------------------- 5 | # Author : Dwi Fahni Denni 6 | # License : Apache v2 7 | # ----------------------------------------------------------------------------- 8 | set -e 9 | 10 | # export CI_PROJECT_PATH="devopscorner" 11 | # export CI_PROJECT_NAME="bookstore" 12 | 13 | # export IMAGE="$CI_PROJECT_PATH/$CI_PROJECT_NAME" 14 | export IMAGE=$2 15 | 16 | docker_build() { 17 | export FILE=$1 18 | export BASE_IMAGE=$3 19 | export TAGS_ID=$4 20 | export CUSTOM_TAGS=$5 21 | 22 | if [ "$CUSTOM_TAGS" = "" ]; then 23 | echo "Build Image => $IMAGE:$BASE_IMAGE" 24 | echo ">> docker build -t $IMAGE:$BASE_IMAGE -f $FILE ." 25 | docker build -t $IMAGE:$BASE_IMAGE -f $FILE . 26 | echo '---' 27 | 28 | echo "Build Image => $IMAGE:$TAGS_ID" 29 | echo ">> docker build -t $IMAGE:$TAGS_ID -f $FILE ." 30 | docker build -t $IMAGE:$TAGS_ID -f $FILE . 31 | echo '---' 32 | 33 | echo "Build Image => $IMAGE:$BASE_IMAGE-$TAGS_ID" 34 | echo ">> docker build -t $IMAGE:$BASE_IMAGE-$TAGS_ID -f $FILE ." 35 | docker build -t $IMAGE:$BASE_IMAGE-$TAGS_ID -f $FILE . 36 | echo '---' 37 | else 38 | echo "Build Image => $IMAGE:$BASE_IMAGE" 39 | echo ">> docker build -t $IMAGE:$BASE_IMAGE -f $FILE ." 40 | docker build -t $IMAGE:$BASE_IMAGE -f $FILE . 41 | echo '---' 42 | 43 | echo "Build Image => $IMAGE:$TAGS_ID" 44 | echo "docker build -t $IMAGE:$TAGS_ID -f $FILE ." 45 | docker build -t $IMAGE:$TAGS_ID -f $FILE . 46 | echo '---' 47 | 48 | echo "Build Image => $IMAGE:$BASE_IMAGE-$TAGS_ID" 49 | echo ">> docker build -t $IMAGE:$BASE_IMAGE-$TAGS_ID -f $FILE ." 50 | docker build -t $IMAGE:$BASE_IMAGE-$TAGS_ID -f $FILE . 51 | echo '---' 52 | 53 | echo "Build Image => $IMAGE:$TAGS_ID-$CUSTOM_TAGS" 54 | docker build -t $IMAGE:$TAGS_ID-$CUSTOM_TAGS -f $FILE . 55 | echo ">> docker build -t $IMAGE:$TAGS_ID-$CUSTOM_TAGS -f $FILE ." 56 | echo '---' 57 | 58 | echo "Build Image => $IMAGE:$BASE_IMAGE-$TAGS_ID-$CUSTOM_TAGS" 59 | echo ">> docker build -t $IMAGE:$BASE_IMAGE-$TAGS_ID-$CUSTOM_TAGS -f $FILE ." 60 | docker build -t $IMAGE:$BASE_IMAGE-$TAGS_ID-$CUSTOM_TAGS -f $FILE . 61 | echo '---' 62 | fi 63 | } 64 | 65 | main() { 66 | # docker_build Dockerfile devopscorner/bookstore alpine [version|latest|tags] [custom-tags] 67 | docker_build $1 $2 $3 $4 $5 68 | echo '' 69 | echo '-- ALL DONE --' 70 | } 71 | 72 | ### START HERE ### 73 | main $1 $2 $3 $4 $5 74 | 75 | ### How to Execute ### 76 | # ./dockerhub-build.sh Dockerfile [DOCKERHUB_IMAGE_PATH] [alpine] [version|latest|tags] [custom-tags] 77 | -------------------------------------------------------------------------------- /dockerhub-push.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # ----------------------------------------------------------------------------- 3 | # Docker Push Container (DockerHub) 4 | # ----------------------------------------------------------------------------- 5 | # Author : Dwi Fahni Denni 6 | # License : Apache v2 7 | # ----------------------------------------------------------------------------- 8 | set -e 9 | 10 | # export CI_PROJECT_PATH="devopscorner" 11 | # export CI_PROJECT_NAME="bookstore" 12 | 13 | # export IMAGE="$CI_PROJECT_PATH/$CI_PROJECT_NAME" 14 | export IMAGE=$1 15 | 16 | login_docker() { 17 | echo '===================' 18 | echo ' Login DockerHub ' 19 | echo '===================' 20 | echo $DOCKERHUB_PASSWORD | docker login --username $DOCKERHUB_USERNAME --password-stdin 21 | echo '- DONE -' 22 | echo '' 23 | } 24 | 25 | docker_push() { 26 | export TAGS_ID=$2 27 | IMAGES=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep $IMAGE:$TAGS_ID) 28 | for IMG in $IMAGES; do 29 | echo "Docker Push => $IMG" 30 | echo ">> docker push $IMG" 31 | docker push $IMG 32 | echo '- DONE -' 33 | echo '' 34 | done 35 | } 36 | 37 | main() { 38 | login_docker 39 | # docker_push devopscorner/bookstore [alpine|version|latest|tags|custom-tags] 40 | docker_push $1 $2 41 | echo '' 42 | echo '-- ALL DONE --' 43 | } 44 | 45 | ### START HERE ### 46 | main $1 $2 47 | 48 | ### How to Execute ### 49 | # ./dockerhub-push.sh [DOCKERHUB_IMAGE_PATH] [alpine|version|latest|tags|custom-tags] 50 | -------------------------------------------------------------------------------- /dockerhub-tag.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # ----------------------------------------------------------------------------- 3 | # Docker Tag Container (DockerHub) 4 | # ----------------------------------------------------------------------------- 5 | # Author : Dwi Fahni Denni 6 | # License : Apache v2 7 | # ----------------------------------------------------------------------------- 8 | set -e 9 | 10 | # export CI_PROJECT_PATH="devopscorner" 11 | # export CI_PROJECT_NAME="bookstore" 12 | 13 | # export IMAGE="$CI_PROJECT_PATH/$CI_PROJECT_NAME" 14 | export IMAGE=$1 15 | 16 | set_tag() { 17 | export BASE_IMAGE=$2 18 | export TAGS_ID=$3 19 | export CUSTOM_TAGS=$4 20 | export COMMIT_HASH=$(git log -1 --format=format:"%H") 21 | 22 | if [ "$CUSTOM_TAGS" = "" ]; then 23 | export TAGS="$TAGS_ID \ 24 | $BASE_IMAGE-$TAGS_ID \ 25 | $TAGS_ID-$COMMIT_HASH \ 26 | $BASE_IMAGE-$COMMIT_HASH " 27 | else 28 | export TAGS="$TAGS_ID \ 29 | $BASE_IMAGE-$TAGS_ID \ 30 | $TAGS_ID-$COMMIT_HASH \ 31 | $BASE_IMAGE-$COMMIT_HASH \ 32 | $TAGS_ID-$CUSTOM_TAGS" 33 | fi 34 | } 35 | 36 | docker_tag() { 37 | for TAG in $TAGS; do 38 | echo "Docker Tags => $IMAGE:$TAG" 39 | echo ">> docker tag $IMAGE:$BASE_IMAGE $IMAGE:$TAG" 40 | docker tag $IMAGE:$BASE_IMAGE $IMAGE:$TAG 41 | echo '- DONE -' 42 | echo '' 43 | done 44 | } 45 | 46 | main() { 47 | # set_tag devopscorner/bookstore alpine [version|latest|tags] [custom-tags] 48 | set_tag $1 $2 $3 $4 49 | docker_tag 50 | echo '' 51 | echo '-- ALL DONE --' 52 | } 53 | 54 | ### START HERE ### 55 | main $1 $2 $3 $4 56 | 57 | ### How to Execute ### 58 | # ./dockerhub-tag.sh [DOCKERHUB_IMAGE_PATH] [alpine] [version|latest|tags] [custom-tags] 59 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Golang Deployment 2 | 3 | Kubernetes Deployment for Simple Golang API 4 | 5 | ![goreport](https://goreportcard.com/badge/github.com/devopscorner/golang-deployment/src) 6 | ![all contributors](https://img.shields.io/github/contributors/devopscorner/golang-deployment) 7 | ![tags](https://img.shields.io/github/v/tag/devopscorner/golang-deployment?sort=semver) 8 | [![docker pulls](https://img.shields.io/docker/pulls/devopscorner/bookstore.svg)](https://hub.docker.com/r/devopscorner/bookstore/) 9 | ![download all](https://img.shields.io/github/downloads/devopscorner/golang-deployment/total.svg) 10 | ![view](https://views.whatilearened.today/views/github/devopscorner/golang-deployment.svg) 11 | ![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) 12 | ![issues](https://img.shields.io/github/issues/devopscorner/golang-deployment) 13 | ![pull requests](https://img.shields.io/github/issues-pr/devopscorner/golang-deployment) 14 | ![forks](https://img.shields.io/github/forks/devopscorner/golang-deployment) 15 | ![stars](https://img.shields.io/github/stars/devopscorner/golang-deployment) 16 | [![license](https://img.shields.io/github/license/devopscorner/golang-deployment)](https://img.shields.io/github/license/devopscorner/golang-deployment) 17 | 18 | --- 19 | 20 | ## INDEX 21 | 22 | - Build Container `devopscorner/cicd` 23 | - Build Container `devopscorner/cicd` for DockerHub, detail [here](https://github.com/devopscorner/devopscorner-container/blob/master/docs/container-cicd-dockerhub.md) 24 | - Build Container `devopscorner/cicd` for ECR, detail [here](https://github.com/devopscorner/devopscorner-container/blob/master/docs/container-cicd-ecr.md) 25 | 26 | - Build Container `devopscorner/bookstore` 27 | - Build Container `devopscorner/bookstore` for DockerHub, detail [here](container-bookstore-dockerhub.md) 28 | - Build Container `devopscorner/bookstore` for ECR, detail [here](container-bookstore-ecr.md) 29 | 30 | - Workflow CI/CD Pipeline, detail [here](workflow-cicd-bookstore-pipeline.md) 31 | 32 | - GitOps & GitOps DevSecOps Flow (Azure DevOps Pipeline), detail [here](gitops-devsecops-flow-azure.md) link 33 | 34 | - Deployments: 35 | - **ArgoCD**, detail [here](deployment-argocd.md) link 36 | - **AWS Developer Tools** (AWS CodeCommit, AWS CodeBuild & AWS CodePipeline), detail [here](deployment-aws-developer-tools.md) link 37 | - **Amazon CodeCatalyst Pipeline**, detail [here](deployment-amazon-codecatalyst.md) link 38 | - **Azure DevOps Pipeline**, detail [here](deployment-azure-devops.md) link 39 | - **Bitbucket Pipeline**, detail [here](deployment-bitbucket.md) link 40 | - **CircleCI Pipeline**, detail [here](deployment-circleci.md) link 41 | - **DroneCI Pipeline**, detail [here](deployment-droneci.md) link 42 | - **GitHub Action**, detail [here](deployment-github.md) link 43 | - **GitLab CI/CD**, detail [here](deployment-gitlab.md) link 44 | - **Jenkins CI & Spinnaker CD**, detail [here](deployment-jenkins-spinnaker.md) link 45 | - **Jenkins CI/CD**, detail [here](deployment-jenkins.md) link 46 | - **OpenShift CI/CD**, detail [here](deployment-openshift.md) link 47 | - **SemaphoreCI**, detail [here](deployment-semaphoreci.md) link 48 | - **Spinnaker CD**, detail [here](deployment-spinnaker.md) link 49 | - **Terraform AWS CodeBuild, AWS CodePipeline & Amazon SNS**, detail [here](deployment-terraform.md) link 50 | - **TravisCI**, detail [here](deployment-travisci.md) link 51 | 52 | 53 | ## Reproduce Testing 54 | 55 | - RESTful API Testing, detail [here](test-restful-api.md) link 56 | -------------------------------------------------------------------------------- /docs/assets/gitops-devsecops-azure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devopscorner/golang-deployment/87a29585a1f5658e2e2c4d2d5bcefeb1d955f0c7/docs/assets/gitops-devsecops-azure.png -------------------------------------------------------------------------------- /docs/assets/gitops-flow-azure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devopscorner/golang-deployment/87a29585a1f5658e2e2c4d2d5bcefeb1d955f0c7/docs/assets/gitops-flow-azure.png -------------------------------------------------------------------------------- /docs/container-bookstore-dockerhub.md: -------------------------------------------------------------------------------- 1 | # Golang Deployment - DockerHub 2 | 3 | Kubernetes Deployment for Simple Golang API 4 | 5 | ![goreport](https://goreportcard.com/badge/github.com/devopscorner/golang-deployment/src) 6 | ![all contributors](https://img.shields.io/github/contributors/devopscorner/golang-deployment) 7 | ![tags](https://img.shields.io/github/v/tag/devopscorner/golang-deployment?sort=semver) 8 | [![docker pulls](https://img.shields.io/docker/pulls/devopscorner/bookstore.svg)](https://hub.docker.com/r/devopscorner/bookstore/) 9 | ![download all](https://img.shields.io/github/downloads/devopscorner/golang-deployment/total.svg) 10 | ![view](https://views.whatilearened.today/views/github/devopscorner/golang-deployment.svg) 11 | ![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) 12 | ![issues](https://img.shields.io/github/issues/devopscorner/golang-deployment) 13 | ![pull requests](https://img.shields.io/github/issues-pr/devopscorner/golang-deployment) 14 | ![forks](https://img.shields.io/github/forks/devopscorner/golang-deployment) 15 | ![stars](https://img.shields.io/github/stars/devopscorner/golang-deployment) 16 | [![license](https://img.shields.io/github/license/devopscorner/golang-deployment)](https://img.shields.io/github/license/devopscorner/golang-deployment) 17 | 18 | --- 19 | 20 | ## Build Container Image 21 | 22 | - Clone this repository 23 | 24 | ``` 25 | git clone https://github.com/devopscorner/golang-deployment.git 26 | ``` 27 | 28 | - Replace "YOUR_AWS_ACCOUNT" with your AWS ACCOUNT ID 29 | 30 | ``` 31 | find ./ -type f -exec sed -i 's/YOUR_AWS_ACCOUNT/123456789012/g' {} \; 32 | ``` 33 | 34 | - Set Environment Variable 35 | 36 | ``` 37 | export ALPINE_VERSION=3.17 # 3.15 | 3.16 | 3.17 38 | export BASE_IMAGE="alpine" 39 | export IMAGE="devopscorner/bookstore" 40 | export TAG="latest" 41 | ``` 42 | 43 | - Execute Build Image 44 | 45 | ``` 46 | # Golang 1.19.3 - Alpine 3.15 47 | docker build -f Dockerfile -t ${IMAGE}:alpine . 48 | docker build -f Dockerfile.alpine-3.15 -t ${IMAGE}:alpine-3.15 . 49 | docker build -f Dockerfile.alpine-3.15 -t ${IMAGE}:golang1.19.3-alpine3.15 . 50 | 51 | # Golang 1.19.5 - Alpine 3.16 52 | docker build -f Dockerfile -t ${IMAGE}:alpine . 53 | docker build -f Dockerfile.alpine-3.16 -t ${IMAGE}:alpine-3.16 . 54 | docker build -f Dockerfile.alpine-3.16 -t ${IMAGE}:golang1.19.5-alpine3.16 . 55 | 56 | # Golang 1.19.5 - Alpine 3.17 57 | docker build -f Dockerfile -t ${IMAGE}:alpine . 58 | docker build -f Dockerfile.alpine-3.17 -t ${IMAGE}:alpine-3.17 . 59 | docker build -f Dockerfile.alpine-3.17 -t ${IMAGE}:golang1.19.5-alpine3.17 . 60 | 61 | -- or -- 62 | 63 | dockerhub-build.sh alpine Dockerfile ${ALPINE_VERSION} 64 | dockerhub-build.sh alpine Dockerfile.alpine-3.15 ${ALPINE_VERSION} 65 | dockerhub-build.sh alpine Dockerfile.alpine-3.16 ${ALPINE_VERSION} 66 | dockerhub-build.sh alpine Dockerfile.alpine-3.17 ${ALPINE_VERSION} 67 | 68 | -- or -- 69 | 70 | # default: 3.17 71 | make dockerhub-build-alpine 72 | ``` 73 | 74 | ## Push Image to DockerHub 75 | 76 | - Login to your DockerHub Account 77 | 78 | - Add Environment Variable 79 | ``` 80 | export DOCKERHUB_USERNAME=[YOUR_DOCKERHUB_USERNAME] 81 | export DOCKERHUB_PASSWORD=[YOUR_DOCKERHUB_PASSWORD_OR_PERSONAL_TOKEN] 82 | ``` 83 | 84 | - Create Tags Image 85 | - Example: 86 | 87 | ``` 88 | # Alpine 89 | docker tag ${IMAGE}:alpine ${IMAGE}:latest 90 | 91 | docker tag ${IMAGE}:alpine ${IMAGE}:alpine-latest 92 | 93 | docker tag ${IMAGE}:alpine ${IMAGE}:alpine-3.16 94 | ``` 95 | 96 | - With Script: 97 | 98 | ``` 99 | # default: 3.16 100 | ./dockerhub-tag.sh alpine ${ALPINE_VERSION} 101 | 102 | -- or -- 103 | 104 | # default: 3.16 105 | make dockerhub-tag-alpine 106 | ``` 107 | 108 | - Push Image to **DockerHub** with Tags 109 | 110 | - Example: 111 | 112 | ``` 113 | # Alpine 114 | docker push devopscorner-bookstore:alpine 115 | 116 | docker push devopscorner-bookstore:latest 117 | 118 | docker push devopscorner-bookstore:alpine-latest 119 | ``` 120 | 121 | - With Script: 122 | 123 | ``` 124 | ./dockerhub-push.sh alpine CI_PATH="devopscorner/bookstore" 125 | 126 | -- or -- 127 | 128 | make dockerhub-push-alpine 129 | ``` 130 | -------------------------------------------------------------------------------- /docs/container-bookstore-ecr.md: -------------------------------------------------------------------------------- 1 | # Golang Deployment - Amazon ECR (Elastic Container Registry) 2 | 3 | Kubernetes Deployment for Simple Golang API 4 | 5 | ![goreport](https://goreportcard.com/badge/github.com/devopscorner/golang-deployment/src) 6 | ![all contributors](https://img.shields.io/github/contributors/devopscorner/golang-deployment) 7 | ![tags](https://img.shields.io/github/v/tag/devopscorner/golang-deployment?sort=semver) 8 | [![docker pulls](https://img.shields.io/docker/pulls/devopscorner/bookstore.svg)](https://hub.docker.com/r/devopscorner/bookstore/) 9 | ![download all](https://img.shields.io/github/downloads/devopscorner/golang-deployment/total.svg) 10 | ![view](https://views.whatilearened.today/views/github/devopscorner/golang-deployment.svg) 11 | ![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) 12 | ![issues](https://img.shields.io/github/issues/devopscorner/golang-deployment) 13 | ![pull requests](https://img.shields.io/github/issues-pr/devopscorner/golang-deployment) 14 | ![forks](https://img.shields.io/github/forks/devopscorner/golang-deployment) 15 | ![stars](https://img.shields.io/github/stars/devopscorner/golang-deployment) 16 | [![license](https://img.shields.io/github/license/devopscorner/golang-deployment)](https://img.shields.io/github/license/devopscorner/golang-deployment) 17 | 18 | --- 19 | 20 | ## Build Container Image 21 | 22 | - Clone this repository 23 | 24 | ``` 25 | git clone https://github.com/devopscorner/golang-deployment.git 26 | ``` 27 | 28 | - Replace "YOUR_AWS_ACCOUNT" with your AWS ACCOUNT ID 29 | 30 | ``` 31 | find ./ -type f -exec sed -i 's/YOUR_AWS_ACCOUNT/123456789012/g' {} \; 32 | ``` 33 | 34 | - Set Environment Variable 35 | 36 | ``` 37 | export ALPINE_VERSION=3.17 # 3.15 | 3.16 | 3.17 38 | export BASE_IMAGE="alpine" 39 | export IMAGE="YOUR_AWS_ACCOUNT.dkr.ecr.ap-southeast-1.amazonaws.com/devopscorner/bookstore" 40 | export TAG="latest" 41 | ``` 42 | 43 | - Execute Build Image 44 | 45 | ``` 46 | # Golang 1.19.3 - Alpine 3.15 47 | docker build -f Dockerfile -t ${IMAGE}:alpine . 48 | docker build -f Dockerfile.alpine-3.15 -t ${IMAGE}:alpine-3.15 . 49 | docker build -f Dockerfile.alpine-3.15 -t ${IMAGE}:golang1.19.3-alpine3.15 . 50 | 51 | # Golang 1.19.5 - Alpine 3.16 52 | docker build -f Dockerfile -t ${IMAGE}:alpine . 53 | docker build -f Dockerfile.alpine-3.16 -t ${IMAGE}:alpine-3.16 . 54 | docker build -f Dockerfile.alpine-3.16 -t ${IMAGE}:golang1.19.5-alpine3.16 . 55 | 56 | # Golang 1.19.5 - Alpine 3.17 57 | docker build -f Dockerfile -t ${IMAGE}:alpine . 58 | docker build -f Dockerfile.alpine-3.17 -t ${IMAGE}:alpine-3.17 . 59 | docker build -f Dockerfile.alpine-3.17 -t ${IMAGE}:golang1.19.5-alpine3.17 . 60 | 61 | -- or -- 62 | 63 | ecr-build.sh ${YOUR_AWS_ACCOUNT} alpine Dockerfile ${ALPINE_VERSION} 64 | ecr-build.sh ${YOUR_AWS_ACCOUNT} alpine Dockerfile.alpine-3.15 ${ALPINE_VERSION} 65 | ecr-build.sh ${YOUR_AWS_ACCOUNT} alpine Dockerfile.alpine-3.16 ${ALPINE_VERSION} 66 | ecr-build.sh ${YOUR_AWS_ACCOUNT} alpine Dockerfile.alpine-3.17 ${ALPINE_VERSION} 67 | 68 | -- or -- 69 | 70 | # default: 3.17 71 | make ecr-build-alpine ARGS=YOUR_AWS_ACCOUNT 72 | ``` 73 | 74 | ## Push Image to Amazon ECR (Elastic Container Registry) 75 | 76 | - Create Tags Image 77 | - Example: 78 | 79 | ``` 80 | # Alpine 81 | docker tag YOUR_AWS_ACCOUNT.dkr.ecr.ap-southeast-1.amazonaws.com/devopscorner/bookstore:alpine YOUR_AWS_ACCOUNT.dkr.ecr.ap-southeast-1.amazonaws.com/devopscorner/bookstore:latest 82 | 83 | docker tag YOUR_AWS_ACCOUNT.dkr.ecr.ap-southeast-1.amazonaws.com/devopscorner/bookstore:alpine YOUR_AWS_ACCOUNT.dkr.ecr.ap-southeast-1.amazonaws.com/devopscorner/bookstore:alpine-latest 84 | 85 | docker tag YOUR_AWS_ACCOUNT.dkr.ecr.ap-southeast-1.amazonaws.com/devopscorner/bookstore:alpine YOUR_AWS_ACCOUNT.dkr.ecr.ap-southeast-1.amazonaws.com/devopscorner/bookstore:alpine-3.16 86 | ``` 87 | 88 | - With Script: 89 | 90 | ``` 91 | # default: 3.17 92 | docker tag ${IMAGE}:${ALPINE_VERSION} 93 | 94 | -- or -- 95 | 96 | # default: 3.17 97 | ./ecr-tag.sh ARGS=YOUR_AWS_ACCOUNT alpine ${ALPINE_VERSION} CI_PATH=devopscorner/bookstore 98 | 99 | -- or -- 100 | 101 | make ecr-tag-alpine ARGS=YOUR_AWS_ACCOUNT CI_PATH=devopscorner/bookstore 102 | ``` 103 | 104 | Push Image to **Amazon ECR** with Tags 105 | 106 | - Example: 107 | 108 | ``` 109 | # Alpine 110 | docker push YOUR_AWS_ACCOUNT.dkr.ecr.ap-southeast-1.amazonaws.com/devopscorner/bookstore:alpine 111 | 112 | docker push YOUR_AWS_ACCOUNT.dkr.ecr.ap-southeast-1.amazonaws.com/devopscorner/bookstore:alpine-latest 113 | 114 | docker push YOUR_AWS_ACCOUNT.dkr.ecr.ap-southeast-1.amazonaws.com/devopscorner/bookstore:alpine-3.16 115 | ``` 116 | 117 | - With Script: 118 | 119 | ``` 120 | ./ecr-push.sh ARGS=YOUR_AWS_ACCOUNT alpine CI_PATH="devopscorner/bookstore" 121 | 122 | -- or -- 123 | 124 | make ecr-push-alpine ARGS=YOUR_AWS_ACCOUNT CI_PATH="devopscorner/bookstore" 125 | ``` 126 | -------------------------------------------------------------------------------- /docs/deployment-amazon-codecatalyst.md: -------------------------------------------------------------------------------- 1 | # Golang Deployment - Deployment with Amazon CodeCatalyst 2 | 3 | Kubernetes Deployment for Simple Golang API 4 | 5 | ![goreport](https://goreportcard.com/badge/github.com/devopscorner/golang-deployment/src) 6 | ![all contributors](https://img.shields.io/github/contributors/devopscorner/golang-deployment) 7 | ![tags](https://img.shields.io/github/v/tag/devopscorner/golang-deployment?sort=semver) 8 | [![docker pulls](https://img.shields.io/docker/pulls/devopscorner/bookstore.svg)](https://hub.docker.com/r/devopscorner/bookstore/) 9 | ![download all](https://img.shields.io/github/downloads/devopscorner/golang-deployment/total.svg) 10 | ![view](https://views.whatilearened.today/views/github/devopscorner/golang-deployment.svg) 11 | ![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) 12 | ![issues](https://img.shields.io/github/issues/devopscorner/golang-deployment) 13 | ![pull requests](https://img.shields.io/github/issues-pr/devopscorner/golang-deployment) 14 | ![forks](https://img.shields.io/github/forks/devopscorner/golang-deployment) 15 | ![stars](https://img.shields.io/github/stars/devopscorner/golang-deployment) 16 | [![license](https://img.shields.io/github/license/devopscorner/golang-deployment)](https://img.shields.io/github/license/devopscorner/golang-deployment) 17 | 18 | --- 19 | 20 | ## Workflow Action 21 | 22 | ``` 23 | Name: Workflow_GitHubActions_Deployment 24 | SchemaVersion: "1.0" 25 | 26 | # Optional - Set automatic triggers. 27 | Triggers: 28 | - Type: PUSH 29 | Branches: 30 | - master 31 | - "release/*" 32 | - Type: PULLREQUEST 33 | Branches: 34 | - "features/*" 35 | - "bugfix/*" 36 | - "hotfix/*" 37 | Events: 38 | - OPEN 39 | - REVISION 40 | 41 | # Required - Define action configurations. 42 | Actions: 43 | GitHubActions_Deployment: 44 | Identifier: aws/github-actions-runner@v1 45 | Inputs: 46 | Sources: 47 | - WorkflowSource 48 | Configuration: 49 | Steps: 50 | - name: Build Container GO Apps 51 | run: | 52 | GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD) 53 | COMMIT_HASH=$(git log -1 --format=format:"%H") 54 | 55 | latestTag=$(git describe --tags `git rev-list --tags --max-count=1`) 56 | if [[ -z "$latestTag" ]]; then 57 | latestTag=1.0.0 58 | fi 59 | 60 | if [[ "$GIT_BRANCH" == "features/"* ]]; then 61 | semver="$latestTag-features.${COMMIT_HASH}" 62 | elif [[ "$GIT_BRANCH" == "bugfix/"* ]]; then 63 | semver="$latestTag-bugfix.${COMMIT_HASH}" 64 | elif [[ "$GIT_BRANCH" == "hotfix/"* ]]; then 65 | semver="$latestTag-beta.${COMMIT_HASH}" 66 | else 67 | semver="$latestTag.${COMMIT_HASH}" 68 | fi 69 | 70 | if [[ -z "$semver" ]]; then 71 | ## DockerHub 72 | ## ./dockerhub-build.sh Dockerfile devopscorner/bookstore alpine $COMMIT_HASH 73 | ./ecr-build.sh "${Secrets.AWS_ACCOUNT_ID}" Dockerfile devopscorner/bookstore alpine $COMMIT_HASH 74 | else 75 | ## DockerHub 76 | ## ./dockerhub-build.sh Dockerfile devopscorner/bookstore alpine $semver 77 | ## ECR 78 | ./ecr-build.sh "${Secrets.AWS_ACCOUNT_ID}" Dockerfile devopscorner/bookstore alpine $semver 79 | fi 80 | - name: List Container Images 81 | run: | 82 | docker images | grep "devopscorner/bookstore" 83 | - name: Push to ECR (Container Registry) 84 | run: | 85 | ./ecr-push.sh "${Secrets.AWS_ACCOUNT_ID}" devopscorner/bookstore alpine 86 | - name: Deploy to ECS 87 | run: | 88 | echo "Deploy to ECS cluster..." 89 | Outputs: 90 | Artifacts: 91 | - Name: "MyArtifact" 92 | Files: 93 | - "_infra/**" 94 | - ".aws/**" 95 | - ".codecatalyst/workflows/**" 96 | - "docs/**" 97 | - "docker-compose.yml" 98 | - "Dockerfile" 99 | - "Dockerfile.alpine-3.15" 100 | - "Dockerfile.alpine-3.16" 101 | - "Dockerfile.alpine-3.17" 102 | - "dockerhub-build.sh" 103 | - "dockerhub-push.sh" 104 | - "dockerhub-tag.sh" 105 | - "ecr-build.sh" 106 | - "ecr-push.sh" 107 | - "ecr-pull.sh" 108 | - "ecr-tag.sh" 109 | - "entrypoint.sh" 110 | - "git-clone.sh" 111 | - "Makefile" 112 | - "run-docker.sh" 113 | - "start-build.sh" 114 | - "src/**" 115 | Compute: 116 | Type: EC2 117 | Fleet: Linux.x86-64.Large 118 | Environment: 119 | Connections: 120 | - Role: CodeCatalystPreviewDevelopmentAdministrator-5nxysr 121 | Name: "YOUR_AWS_ACCOUNT" 122 | Name: staging-dev 123 | DeployToECS: 124 | DependsOn: 125 | - GitHubActions_Deployment 126 | Identifier: aws/ecs-deploy@v1 127 | Environment: 128 | Connections: 129 | - Role: CodeCatalystPreviewDevelopmentAdministrator-5nxysr 130 | Name: "YOUR_AWS_ACCOUNT" 131 | Name: staging-dev 132 | Inputs: 133 | Sources: 134 | - WorkflowSource 135 | Artifacts: 136 | - MyArtifact 137 | Configuration: 138 | region: us-west-2 139 | cluster: codecatalyst-ecs-cluster 140 | service: bookstore-ecs-service 141 | task-definition: .codecatalyst/workflows/taskdef.json 142 | ``` 143 | 144 | ## Task Definition 145 | 146 | ``` 147 | { 148 | "executionRoleArn": "arn:aws:iam::YOUR_AWS_ACCOUNT:role/codecatalyst-ecs-task-execution-role", 149 | "containerDefinitions": [ 150 | { 151 | "name": "codecatalyst-ecs-container", 152 | "image": "YOUR_AWS_ACCOUNT.dkr.ecr.us-west-2.amazonaws.com/devopscorner/bookstore-codecatalyst:alpine", 153 | "essential": true, 154 | "portMappings": [ 155 | { 156 | "hostPort": 8080, 157 | "protocol": "tcp", 158 | "containerPort": 8080 159 | } 160 | ] 161 | } 162 | ], 163 | "requiresCompatibilities": [ 164 | "FARGATE" 165 | ], 166 | "cpu": "256", 167 | "family": "bookstore-ecs-task-def", 168 | "memory": "512", 169 | "networkMode": "awsvpc" 170 | } 171 | ``` 172 | -------------------------------------------------------------------------------- /docs/deployment-argocd.md: -------------------------------------------------------------------------------- 1 | # Golang Deployment - Deployment with ArgoCD Pipeline 2 | 3 | Kubernetes Deployment for Simple Golang API 4 | 5 | ![goreport](https://goreportcard.com/badge/github.com/devopscorner/golang-deployment/src) 6 | ![all contributors](https://img.shields.io/github/contributors/devopscorner/golang-deployment) 7 | ![tags](https://img.shields.io/github/v/tag/devopscorner/golang-deployment?sort=semver) 8 | [![docker pulls](https://img.shields.io/docker/pulls/devopscorner/bookstore.svg)](https://hub.docker.com/r/devopscorner/bookstore/) 9 | ![download all](https://img.shields.io/github/downloads/devopscorner/golang-deployment/total.svg) 10 | ![view](https://views.whatilearened.today/views/github/devopscorner/golang-deployment.svg) 11 | ![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) 12 | ![issues](https://img.shields.io/github/issues/devopscorner/golang-deployment) 13 | ![pull requests](https://img.shields.io/github/issues-pr/devopscorner/golang-deployment) 14 | ![forks](https://img.shields.io/github/forks/devopscorner/golang-deployment) 15 | ![stars](https://img.shields.io/github/stars/devopscorner/golang-deployment) 16 | [![license](https://img.shields.io/github/license/devopscorner/golang-deployment)](https://img.shields.io/github/license/devopscorner/golang-deployment) 17 | 18 | --- 19 | 20 | ## Example CI/CD Script `cicd-argocd.yml` 21 | 22 | ``` 23 | apiVersion: argoproj.io/v1alpha1 24 | kind: Application 25 | metadata: 26 | name: bookstore 27 | namespace: argocd 28 | spec: 29 | project: default 30 | source: 31 | repoURL: https://github.com/devopscorner/golang-deployment.git 32 | path: manifests 33 | targetRevision: HEAD 34 | destination: 35 | server: https://your-kubernetes-server 36 | namespace: bookstore 37 | syncPolicy: 38 | automated: 39 | prune: true 40 | selfHeal: true 41 | syncOptions: 42 | - Validate=false 43 | healthChecks: 44 | - type: DeploymentRollout 45 | deploymentName: bookstore-deployment 46 | ignoreDifferences: 47 | - kind: Service 48 | jsonPointers: 49 | - /spec/clusterIP 50 | - kind: Deployment 51 | jsonPointers: 52 | - /spec/selector/matchLabels 53 | appVersion: ${VERSION} 54 | ``` -------------------------------------------------------------------------------- /docs/deployment-azure-devops.md: -------------------------------------------------------------------------------- 1 | # Golang Deployment - Deployment with Azure DevOps Pipeline 2 | 3 | Kubernetes Deployment for Simple Golang API 4 | 5 | ![goreport](https://goreportcard.com/badge/github.com/devopscorner/golang-deployment/src) 6 | ![all contributors](https://img.shields.io/github/contributors/devopscorner/golang-deployment) 7 | ![tags](https://img.shields.io/github/v/tag/devopscorner/golang-deployment?sort=semver) 8 | [![docker pulls](https://img.shields.io/docker/pulls/devopscorner/bookstore.svg)](https://hub.docker.com/r/devopscorner/bookstore/) 9 | ![download all](https://img.shields.io/github/downloads/devopscorner/golang-deployment/total.svg) 10 | ![view](https://views.whatilearened.today/views/github/devopscorner/golang-deployment.svg) 11 | ![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) 12 | ![issues](https://img.shields.io/github/issues/devopscorner/golang-deployment) 13 | ![pull requests](https://img.shields.io/github/issues-pr/devopscorner/golang-deployment) 14 | ![forks](https://img.shields.io/github/forks/devopscorner/golang-deployment) 15 | ![stars](https://img.shields.io/github/stars/devopscorner/golang-deployment) 16 | [![license](https://img.shields.io/github/license/devopscorner/golang-deployment)](https://img.shields.io/github/license/devopscorner/golang-deployment) 17 | 18 | --- 19 | 20 | ## Example CI/CD Script `cicd-azure-devops.yml` 21 | 22 | ``` 23 | trigger: 24 | branches: 25 | include: 26 | - features/* 27 | - bugfix/* 28 | - hotfix/* 29 | 30 | pool: 31 | vmImage: 'ubuntu-latest' 32 | 33 | variables: 34 | imageName: 'devopcorner/bookstore' 35 | ecrRegistry: '0987612345.dkr.ecr.ap-southeast-1.amazonaws.com' 36 | helmReleaseName: 'bookstore' 37 | 38 | steps: 39 | - script: | 40 | go build -o app 41 | displayName: 'Build Go Application' 42 | 43 | - script: | 44 | if [[ "$(Build.SourceBranch)" == "refs/heads/features/"* ]]; then 45 | semver=1.0.0-$(Build.SourceBranchName).$(Build.SourceVersion) 46 | elif [[ "$(Build.SourceBranch)" == "refs/heads/bugfix/"* ]]; then 47 | semver=1.1.0-$(Build.SourceBranchName).$(Build.SourceVersion) 48 | elif [[ "$(Build.SourceBranch)" == "refs/heads/hotfix/"* ]]; then 49 | semver=1.1.1-$(Build.SourceBranchName).$(Build.SourceVersion) 50 | fi 51 | echo "Semantic version: $semver" 52 | echo "##vso[task.setvariable variable=imageTag]$semver" 53 | displayName: 'Set Semantic Version' 54 | 55 | - task: Docker@2 56 | inputs: 57 | containerRegistry: 'ECR' 58 | repository: $(imageName) 59 | command: 'build' 60 | Dockerfile: '$(System.DefaultWorkingDirectory)/Dockerfile' 61 | tags: | 62 | $(imageTag) 63 | $(Build.SourceBranchName)-latest 64 | latest 65 | displayName: 'Build and Push Docker Image' 66 | 67 | - task: HelmInstaller@1 68 | inputs: 69 | helmVersionToInstall: 'v3.7.0' 70 | displayName: 'Install Helm' 71 | 72 | - script: | 73 | helmfile sync 74 | workingDirectory: '$(System.DefaultWorkingDirectory)/helm' 75 | displayName: 'Deploy to EKS using Helmfile' 76 | ``` -------------------------------------------------------------------------------- /docs/deployment-bitbucket.md: -------------------------------------------------------------------------------- 1 | # Golang Deployment - Deployment with Bitbucket Pipeline 2 | 3 | Kubernetes Deployment for Simple Golang API 4 | 5 | ![goreport](https://goreportcard.com/badge/github.com/devopscorner/golang-deployment/src) 6 | ![all contributors](https://img.shields.io/github/contributors/devopscorner/golang-deployment) 7 | ![tags](https://img.shields.io/github/v/tag/devopscorner/golang-deployment?sort=semver) 8 | [![docker pulls](https://img.shields.io/docker/pulls/devopscorner/bookstore.svg)](https://hub.docker.com/r/devopscorner/bookstore/) 9 | ![download all](https://img.shields.io/github/downloads/devopscorner/golang-deployment/total.svg) 10 | ![view](https://views.whatilearened.today/views/github/devopscorner/golang-deployment.svg) 11 | ![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) 12 | ![issues](https://img.shields.io/github/issues/devopscorner/golang-deployment) 13 | ![pull requests](https://img.shields.io/github/issues-pr/devopscorner/golang-deployment) 14 | ![forks](https://img.shields.io/github/forks/devopscorner/golang-deployment) 15 | ![stars](https://img.shields.io/github/stars/devopscorner/golang-deployment) 16 | [![license](https://img.shields.io/github/license/devopscorner/golang-deployment)](https://img.shields.io/github/license/devopscorner/golang-deployment) 17 | 18 | --- 19 | 20 | ## Example CI/CD Script `cicd-bitbucket.yml` 21 | 22 | ``` 23 | pipelines: 24 | default: 25 | - step: 26 | name: Build and Deploy 27 | image: golang:1.17 28 | script: 29 | - go build -o app 30 | - | 31 | if [[ "$BITBUCKET_BRANCH" == "features/"* ]]; then 32 | semver=1.0.0-${BITBUCKET_BRANCH#features/}.${BITBUCKET_COMMIT:0:8} 33 | elif [[ "$BITBUCKET_BRANCH" == "bugfix/"* ]]; then 34 | semver=1.1.0-${BITBUCKET_BRANCH#bugfix/}.${BITBUCKET_COMMIT:0:8} 35 | elif [[ "$BITBUCKET_BRANCH" == "hotfix/"* ]]; then 36 | semver=1.1.1-${BITBUCKET_BRANCH#hotfix/}.${BITBUCKET_COMMIT:0:8} 37 | fi 38 | echo "Semantic version: $semver" 39 | echo "imageTag=$semver" >> $BITBUCKET_CLONE_DIR/variables.env 40 | aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com 41 | docker build -t $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver . 42 | docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver 43 | docker tag $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:latest 44 | docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:latest 45 | artifacts: 46 | - app 47 | services: 48 | - docker 49 | caches: 50 | - docker 51 | - go 52 | deployment: production 53 | trigger: manual 54 | environment: 55 | name: production 56 | url: $BITBUCKET_DEPLOYMENT_ENVIRONMENT_URL 57 | ``` -------------------------------------------------------------------------------- /docs/deployment-circleci.md: -------------------------------------------------------------------------------- 1 | # Golang Deployment - Deployment with CircleCI Pipeline 2 | 3 | Kubernetes Deployment for Simple Golang API 4 | 5 | ![goreport](https://goreportcard.com/badge/github.com/devopscorner/golang-deployment/src) 6 | ![all contributors](https://img.shields.io/github/contributors/devopscorner/golang-deployment) 7 | ![tags](https://img.shields.io/github/v/tag/devopscorner/golang-deployment?sort=semver) 8 | [![docker pulls](https://img.shields.io/docker/pulls/devopscorner/bookstore.svg)](https://hub.docker.com/r/devopscorner/bookstore/) 9 | ![download all](https://img.shields.io/github/downloads/devopscorner/golang-deployment/total.svg) 10 | ![view](https://views.whatilearened.today/views/github/devopscorner/golang-deployment.svg) 11 | ![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) 12 | ![issues](https://img.shields.io/github/issues/devopscorner/golang-deployment) 13 | ![pull requests](https://img.shields.io/github/issues-pr/devopscorner/golang-deployment) 14 | ![forks](https://img.shields.io/github/forks/devopscorner/golang-deployment) 15 | ![stars](https://img.shields.io/github/stars/devopscorner/golang-deployment) 16 | [![license](https://img.shields.io/github/license/devopscorner/golang-deployment)](https://img.shields.io/github/license/devopscorner/golang-deployment) 17 | 18 | --- 19 | 20 | ## Example CI/CD Script `cicd-circleci.yml` 21 | 22 | ``` 23 | version: 2.1 24 | jobs: 25 | build-and-deploy: 26 | docker: 27 | - image: circleci/golang:1.19.5 28 | 29 | environment: 30 | AWS_REGION: ap-southeast-1 31 | AWS_ACCOUNT_ID: 0987612345 32 | IMAGE_NAME: devopscorner/bookstore 33 | 34 | steps: 35 | - checkout 36 | - run: 37 | name: Build and push Docker image 38 | command: | 39 | go build -o app 40 | if [[ "$CIRCLE_BRANCH" == "main" ]]; then 41 | semver=1.0.0-${CIRCLE_SHA1:0:8} 42 | elif [[ "$CIRCLE_BRANCH" == "features/"* ]]; then 43 | semver=1.0.0-${CIRCLE_BRANCH#features/}.${CIRCLE_SHA1:0:8} 44 | elif [[ "$CIRCLE_BRANCH" == "bugfix/"* ]]; then 45 | semver=1.1.0-${CIRCLE_BRANCH#bugfix/}.${CIRCLE_SHA1:0:8} 46 | elif [[ "$CIRCLE_BRANCH" == "hotfix/"* ]]; then 47 | semver=1.1.1-${CIRCLE_BRANCH#hotfix/}.${CIRCLE_SHA1:0:8} 48 | fi 49 | echo "Semantic version: $semver" 50 | echo "imageTag=$semver" >> $HOME/variables.env 51 | aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com 52 | docker build -t $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver . 53 | docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver 54 | docker tag $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:latest 55 | docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:latest 56 | 57 | - run: 58 | name: Deploy to Kubernetes using Helmfile 59 | command: | 60 | curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash 61 | helmfile sync 62 | 63 | - store_artifacts: 64 | path: app 65 | 66 | workflows: 67 | build-and-deploy: 68 | jobs: 69 | - build-and-deploy: 70 | requires: 71 | - checkout 72 | filters: 73 | branches: 74 | only: 75 | - main 76 | - /^features\/.*$/ 77 | - /^bugfix\/.*$/ 78 | - /^hotfix\/.*$/ 79 | ``` -------------------------------------------------------------------------------- /docs/deployment-droneci.md: -------------------------------------------------------------------------------- 1 | # Golang Deployment - Deployment with DroneCI Pipeline 2 | 3 | Kubernetes Deployment for Simple Golang API 4 | 5 | ![goreport](https://goreportcard.com/badge/github.com/devopscorner/golang-deployment/src) 6 | ![all contributors](https://img.shields.io/github/contributors/devopscorner/golang-deployment) 7 | ![tags](https://img.shields.io/github/v/tag/devopscorner/golang-deployment?sort=semver) 8 | [![docker pulls](https://img.shields.io/docker/pulls/devopscorner/bookstore.svg)](https://hub.docker.com/r/devopscorner/bookstore/) 9 | ![download all](https://img.shields.io/github/downloads/devopscorner/golang-deployment/total.svg) 10 | ![view](https://views.whatilearened.today/views/github/devopscorner/golang-deployment.svg) 11 | ![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) 12 | ![issues](https://img.shields.io/github/issues/devopscorner/golang-deployment) 13 | ![pull requests](https://img.shields.io/github/issues-pr/devopscorner/golang-deployment) 14 | ![forks](https://img.shields.io/github/forks/devopscorner/golang-deployment) 15 | ![stars](https://img.shields.io/github/stars/devopscorner/golang-deployment) 16 | [![license](https://img.shields.io/github/license/devopscorner/golang-deployment)](https://img.shields.io/github/license/devopscorner/golang-deployment) 17 | 18 | --- 19 | 20 | ## Example CI/CD Script `cicd-droneci.yml` 21 | 22 | ``` 23 | kind: pipeline 24 | type: docker 25 | name: bookstore 26 | 27 | platform: 28 | os: linux 29 | arch: amd64 30 | 31 | steps: 32 | - name: build 33 | image: golang:1.19.5 34 | commands: 35 | - go mod download 36 | - if [ "${DRONE_BRANCH}" = "main" ]; then 37 | semver=1.0.0-${DRONE_COMMIT_SHA:0:8}; 38 | elif [[ "${DRONE_BRANCH}" == "features/"* ]]; then 39 | semver=1.0.0-${DRONE_BRANCH#features/}.${DRONE_COMMIT_SHA:0:8}; 40 | elif [[ "${DRONE_BRANCH}" == "bugfix/"* ]]; then 41 | semver=1.1.0-${DRONE_BRANCH#bugfix/}.${DRONE_COMMIT_SHA:0:8}; 42 | elif [[ "${DRONE_BRANCH}" == "hotfix/"* ]]; then 43 | semver=1.1.1-${DRONE_BRANCH#hotfix/}.${DRONE_COMMIT_SHA:0:8}; 44 | fi 45 | - echo "Semantic version: $semver" 46 | - docker build -t ${PLUGIN_REGISTRY}/${PLUGIN_REPO}:${semver} . 47 | - docker tag ${PLUGIN_REGISTRY}/${PLUGIN_REPO}:${semver} ${PLUGIN_REGISTRY}/${PLUGIN_REPO}:latest 48 | environment: 49 | - PLUGIN_REGISTRY=your-registry-url 50 | - PLUGIN_REPO=bookstore 51 | when: 52 | event: 53 | - push 54 | - tag 55 | 56 | - name: publish 57 | image: plugins/ecr 58 | settings: 59 | region: ap-southeast-1 60 | access_key: 61 | from_secret: aws_access_key 62 | secret_key: 63 | from_secret: aws_secret_key 64 | repo: ${PLUGIN_REGISTRY}/${PLUGIN_REPO} 65 | tags: latest,${semver} 66 | when: 67 | event: 68 | - push 69 | - tag 70 | 71 | - name: deploy 72 | image: dtzar/helm-kubectl 73 | settings: 74 | kubernetes_server: your-kubernetes-server 75 | kubernetes_cert: 76 | from_secret: kubernetes_cert 77 | kubernetes_token: 78 | from_secret: kubernetes_token 79 | kubernetes_namespace: bookstore 80 | command: helmfile sync 81 | when: 82 | event: 83 | - push 84 | - tag 85 | ``` -------------------------------------------------------------------------------- /docs/deployment-github.md: -------------------------------------------------------------------------------- 1 | # Golang Deployment - Deployment with GitHub Action 2 | 3 | Kubernetes Deployment for Simple Golang API 4 | 5 | ![goreport](https://goreportcard.com/badge/github.com/devopscorner/golang-deployment/src) 6 | ![all contributors](https://img.shields.io/github/contributors/devopscorner/golang-deployment) 7 | ![tags](https://img.shields.io/github/v/tag/devopscorner/golang-deployment?sort=semver) 8 | [![docker pulls](https://img.shields.io/docker/pulls/devopscorner/bookstore.svg)](https://hub.docker.com/r/devopscorner/bookstore/) 9 | ![download all](https://img.shields.io/github/downloads/devopscorner/golang-deployment/total.svg) 10 | ![view](https://views.whatilearened.today/views/github/devopscorner/golang-deployment.svg) 11 | ![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) 12 | ![issues](https://img.shields.io/github/issues/devopscorner/golang-deployment) 13 | ![pull requests](https://img.shields.io/github/issues-pr/devopscorner/golang-deployment) 14 | ![forks](https://img.shields.io/github/forks/devopscorner/golang-deployment) 15 | ![stars](https://img.shields.io/github/stars/devopscorner/golang-deployment) 16 | [![license](https://img.shields.io/github/license/devopscorner/golang-deployment)](https://img.shields.io/github/license/devopscorner/golang-deployment) 17 | 18 | --- 19 | 20 | ## Example CI/CD Script `cicd-github.yml` 21 | 22 | ``` 23 | name: CI/CD Pipeline 24 | 25 | on: 26 | push: 27 | branches: 28 | - features/* 29 | - bugfix/* 30 | - hotfix/* 31 | 32 | jobs: 33 | build-and-deploy: 34 | runs-on: ubuntu-latest 35 | 36 | env: 37 | imageName: 'devopcorner/bookstore' 38 | ecrRegistry: '0987612345.dkr.ecr.ap-southeast-1.amazonaws.com' 39 | helmReleaseName: 'bookstore' 40 | 41 | steps: 42 | - name: Checkout Code 43 | uses: actions/checkout@v2 44 | with: 45 | fetch-depth: 0 46 | 47 | - name: Set up Go 48 | uses: actions/setup-go@v2 49 | with: 50 | go-version: 1.17 51 | 52 | - name: Build Go Application 53 | run: go build -o app 54 | 55 | - name: Set Semantic Version 56 | run: | 57 | if [[ "$GITHUB_REF" == "refs/heads/features/"* ]]; then 58 | semver=1.0.0-${GITHUB_REF#refs/heads/features/}.${GITHUB_SHA::8} 59 | elif [[ "$GITHUB_REF" == "refs/heads/bugfix/"* ]]; then 60 | semver=1.1.0-${GITHUB_REF#refs/heads/bugfix/}.${GITHUB_SHA::8} 61 | elif [[ "$GITHUB_REF" == "refs/heads/hotfix/"* ]]; then 62 | semver=1.1.1-${GITHUB_REF#refs/heads/hotfix/}.${GITHUB_SHA::8} 63 | fi 64 | echo "::set-env name=imageTag::$semver" 65 | 66 | - name: Build and Push Docker Image 67 | uses: docker/build-push-action@v2 68 | with: 69 | context: . 70 | push: true 71 | tags: | 72 | ${{ env.imageName }}:${{ env.imageTag }} 73 | ${{ env.imageName }}:${{ github.ref }}-latest 74 | ${{ env.imageName }}:latest 75 | registry: ${{ env.ecrRegistry }} 76 | username: ${{ secrets.ECR_USERNAME }} 77 | password: ${{ secrets.ECR_PASSWORD }} 78 | 79 | - name: Install Helm 80 | uses: appleboy/ssh-action@master 81 | with: 82 | host: ${{ secrets.EKS_HOST }} 83 | username: ${{ secrets.EKS_USERNAME }} 84 | key: ${{ secrets.EKS_PRIVATE_KEY }} 85 | script: | 86 | curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash 87 | 88 | - name: Deploy to EKS using Helmfile 89 | uses: appleboy/ssh-action@master 90 | with: 91 | host: ${{ secrets.EKS_HOST }} 92 | username: ${{ secrets.EKS_USERNAME }} 93 | key: ${{ secrets.EKS_PRIVATE_KEY }} 94 | script: | 95 | helmfile sync 96 | ``` 97 | -------------------------------------------------------------------------------- /docs/deployment-gitlab.md: -------------------------------------------------------------------------------- 1 | # Golang Deployment - Deployment with GitLab CI/CD 2 | 3 | Kubernetes Deployment for Simple Golang API 4 | 5 | ![goreport](https://goreportcard.com/badge/github.com/devopscorner/golang-deployment/src) 6 | ![all contributors](https://img.shields.io/github/contributors/devopscorner/golang-deployment) 7 | ![tags](https://img.shields.io/github/v/tag/devopscorner/golang-deployment?sort=semver) 8 | [![docker pulls](https://img.shields.io/docker/pulls/devopscorner/bookstore.svg)](https://hub.docker.com/r/devopscorner/bookstore/) 9 | ![download all](https://img.shields.io/github/downloads/devopscorner/golang-deployment/total.svg) 10 | ![view](https://views.whatilearened.today/views/github/devopscorner/golang-deployment.svg) 11 | ![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) 12 | ![issues](https://img.shields.io/github/issues/devopscorner/golang-deployment) 13 | ![pull requests](https://img.shields.io/github/issues-pr/devopscorner/golang-deployment) 14 | ![forks](https://img.shields.io/github/forks/devopscorner/golang-deployment) 15 | ![stars](https://img.shields.io/github/stars/devopscorner/golang-deployment) 16 | [![license](https://img.shields.io/github/license/devopscorner/golang-deployment)](https://img.shields.io/github/license/devopscorner/golang-deployment) 17 | 18 | --- 19 | 20 | ## Example CI/CD Script `cicd-gitlab.yml` 21 | 22 | ``` 23 | stages: 24 | - build 25 | - deploy 26 | 27 | variables: 28 | imageName: 'devopcorner/bookstore' 29 | ecrRegistry: '0987612345.dkr.ecr.ap-southeast-1.amazonaws.com' 30 | helmReleaseName: 'bookstore' 31 | 32 | build: 33 | stage: build 34 | image: golang:1.17 35 | script: 36 | - go build -o app 37 | - | 38 | if [[ "$CI_COMMIT_REF_NAME" == "features/"* ]]; then 39 | semver=1.0.0-${CI_COMMIT_REF_NAME#features/}.${CI_COMMIT_SHORT_SHA} 40 | elif [[ "$CI_COMMIT_REF_NAME" == "bugfix/"* ]]; then 41 | semver=1.1.0-${CI_COMMIT_REF_NAME#bugfix/}.${CI_COMMIT_SHORT_SHA} 42 | elif [[ "$CI_COMMIT_REF_NAME" == "hotfix/"* ]]; then 43 | semver=1.1.1-${CI_COMMIT_REF_NAME#hotfix/}.${CI_COMMIT_SHORT_SHA} 44 | fi 45 | echo "Semantic version: $semver" 46 | echo "imageTag=$semver" >> $CI_ENVIRONMENT_URL/variables.env 47 | docker build -t $ecrRegistry/$imageName:$semver . 48 | docker push $ecrRegistry/$imageName:$semver 49 | docker tag $ecrRegistry/$imageName:$semver $ecrRegistry/$imageName:latest 50 | docker push $ecrRegistry/$imageName:latest 51 | artifacts: 52 | paths: 53 | - app 54 | 55 | deploy: 56 | stage: deploy 57 | image: alpine/helm:3.7.0 58 | script: 59 | - apk add --update openssh-client 60 | - ssh-keyscan $EKS_HOST >> ~/.ssh/known_hosts 61 | - ssh -i $EKS_PRIVATE_KEY $EKS_USERNAME@$EKS_HOST "curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash" 62 | - ssh -i $EKS_PRIVATE_KEY $EKS_USERNAME@$EKS_HOST "helmfile sync" 63 | environment: 64 | name: production 65 | url: $CI_ENVIRONMENT_URL 66 | dependencies: 67 | - build 68 | only: 69 | - main 70 | when: manual 71 | ``` -------------------------------------------------------------------------------- /docs/deployment-jenkins-spinnaker.md: -------------------------------------------------------------------------------- 1 | # Golang Deployment - CI with Jenkins, CD with Spinnaker 2 | 3 | Kubernetes Deployment for Simple Golang API 4 | 5 | ![goreport](https://goreportcard.com/badge/github.com/devopscorner/golang-deployment/src) 6 | ![all contributors](https://img.shields.io/github/contributors/devopscorner/golang-deployment) 7 | ![tags](https://img.shields.io/github/v/tag/devopscorner/golang-deployment?sort=semver) 8 | [![docker pulls](https://img.shields.io/docker/pulls/devopscorner/bookstore.svg)](https://hub.docker.com/r/devopscorner/bookstore/) 9 | ![download all](https://img.shields.io/github/downloads/devopscorner/golang-deployment/total.svg) 10 | ![view](https://views.whatilearened.today/views/github/devopscorner/golang-deployment.svg) 11 | ![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) 12 | ![issues](https://img.shields.io/github/issues/devopscorner/golang-deployment) 13 | ![pull requests](https://img.shields.io/github/issues-pr/devopscorner/golang-deployment) 14 | ![forks](https://img.shields.io/github/forks/devopscorner/golang-deployment) 15 | ![stars](https://img.shields.io/github/stars/devopscorner/golang-deployment) 16 | [![license](https://img.shields.io/github/license/devopscorner/golang-deployment)](https://img.shields.io/github/license/devopscorner/golang-deployment) 17 | 18 | --- 19 | 20 | ## Jenkins CI & Spinnaker CD 21 | 22 | - [Jenkins CI AWS CodeCommit](../.jenkins/jenkins-ci-aws-codecommit.jenkinsfile) 23 | - [Jenkins CI Azure DevOps](../.jenkins/jenkins-ci-azure-devops.jenkinsfile) 24 | - [Jenkins CI Bitbucket](../.jenkins/jenkins-ci-bitbucket.jenkinsfile) 25 | - [Jenkins CI GitHub](../.jenkins/jenkins-ci-github.jenkinsfile) 26 | - [Jenkins CI GitLab](../.jenkins/jenkins-ci-gitlab.jenkinsfile) -------------------------------------------------------------------------------- /docs/deployment-jenkins.md: -------------------------------------------------------------------------------- 1 | # Golang Deployment - CI/CD with Jenkins 2 | 3 | Kubernetes Deployment for Simple Golang API 4 | 5 | ![goreport](https://goreportcard.com/badge/github.com/devopscorner/golang-deployment/src) 6 | ![all contributors](https://img.shields.io/github/contributors/devopscorner/golang-deployment) 7 | ![tags](https://img.shields.io/github/v/tag/devopscorner/golang-deployment?sort=semver) 8 | [![docker pulls](https://img.shields.io/docker/pulls/devopscorner/bookstore.svg)](https://hub.docker.com/r/devopscorner/bookstore/) 9 | ![download all](https://img.shields.io/github/downloads/devopscorner/golang-deployment/total.svg) 10 | ![view](https://views.whatilearened.today/views/github/devopscorner/golang-deployment.svg) 11 | ![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) 12 | ![issues](https://img.shields.io/github/issues/devopscorner/golang-deployment) 13 | ![pull requests](https://img.shields.io/github/issues-pr/devopscorner/golang-deployment) 14 | ![forks](https://img.shields.io/github/forks/devopscorner/golang-deployment) 15 | ![stars](https://img.shields.io/github/stars/devopscorner/golang-deployment) 16 | [![license](https://img.shields.io/github/license/devopscorner/golang-deployment)](https://img.shields.io/github/license/devopscorner/golang-deployment) 17 | 18 | --- 19 | 20 | ## Jenkins CI/CD 21 | 22 | - [Jenkins CI/CD](../.jenkins/jenkins-cicd.jenkinsfile) 23 | 24 | ## Example CI/CD Script `cicd-jenkins.jenkinsfile` 25 | 26 | ``` 27 | pipeline { 28 | agent any 29 | 30 | environment { 31 | AWS_REGION = 'ap-southeast-1' 32 | AWS_ACCOUNT_ID = '0987612345' 33 | IMAGE_NAME = 'devopscorner/bookstore' 34 | } 35 | 36 | stages { 37 | stage('Build') { 38 | steps { 39 | sh 'go build -o app' 40 | script { 41 | if (env.BRANCH_NAME ==~ /features\/.*/) { 42 | def semver = "1.0.0-${env.BRANCH_NAME.replace('features/', '')}.${env.GIT_COMMIT}" 43 | echo "Semantic version: ${semver}" 44 | sh "echo imageTag=${semver} >> variables.env" 45 | } else if (env.BRANCH_NAME ==~ /bugfix\/.*/) { 46 | def semver = "1.1.0-${env.BRANCH_NAME.replace('bugfix/', '')}.${env.GIT_COMMIT}" 47 | echo "Semantic version: ${semver}" 48 | sh "echo imageTag=${semver} >> variables.env" 49 | } else if (env.BRANCH_NAME ==~ /hotfix\/.*/) { 50 | def semver = "1.1.1-${env.BRANCH_NAME.replace('hotfix/', '')}.${env.GIT_COMMIT}" 51 | echo "Semantic version: ${semver}" 52 | sh "echo imageTag=${semver} >> variables.env" 53 | } else if (env.BRANCH_NAME == 'main') { 54 | def semver = "1.0.0-${env.GIT_COMMIT}" 55 | echo "Semantic version: ${semver}" 56 | sh "echo imageTag=${semver} >> variables.env" 57 | } 58 | } 59 | withCredentials([usernamePassword(credentialsId: 'aws-ecr-login', usernameVariable: 'AWS_ACCESS_KEY_ID', passwordVariable: 'AWS_SECRET_ACCESS_KEY')]) { 60 | sh "aws ecr get-login-password --region ${env.AWS_REGION} | docker login --username AWS --password-stdin ${env.AWS_ACCOUNT_ID}.dkr.ecr.${env.AWS_REGION}.amazonaws.com" 61 | sh "docker build -t ${env.AWS_ACCOUNT_ID}.dkr.ecr.${env.AWS_REGION}.amazonaws.com/${env.IMAGE_NAME}:${imageTag} ." 62 | sh "docker push ${env.AWS_ACCOUNT_ID}.dkr.ecr.${env.AWS_REGION}.amazonaws.com/${env.IMAGE_NAME}:${imageTag}" 63 | sh "docker tag ${env.AWS_ACCOUNT_ID}.dkr.ecr.${env.AWS_REGION}.amazonaws.com/${env.IMAGE_NAME}:${imageTag} ${env.AWS_ACCOUNT_ID}.dkr.ecr.${env.AWS_REGION}.amazonaws.com/${env.IMAGE_NAME}:latest" 64 | sh "docker push ${env.AWS_ACCOUNT_ID}.dkr.ecr.${env.AWS_REGION}.amazonaws.com/${env.IMAGE_NAME}:latest" 65 | } 66 | stash includes: 'app', name: 'app' 67 | } 68 | } 69 | stage('Deploy') { 70 | steps { 71 | unstash 'app' 72 | withAWS(region: env.AWS_REGION, roleAccount: "${env.AWS_ACCOUNT_ID}") { 73 | sh "curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash" 74 | sh "helmfile sync" 75 | } 76 | } 77 | environment { 78 | DEPLOYMENT_ENVIRONMENT_URL = "http://your-deployment-environment-url" 79 | } 80 | post { 81 | success { 82 | script { 83 | currentBuild.description = "Deployment completed successfully. Visit ${env.DEPLOYMENT_ENVIRONMENT_URL} for details." 84 | } 85 | } 86 | failure { 87 | script { 88 | currentBuild.description = "Deployment failed. Visit ${env.DEPLOYMENT_ENVIRONMENT_URL} for details." 89 | } 90 | } 91 | } 92 | } 93 | } 94 | } 95 | ``` -------------------------------------------------------------------------------- /docs/deployment-openshift.md: -------------------------------------------------------------------------------- 1 | # Golang Deployment - Deployment with OpenShift Pipeline 2 | 3 | Kubernetes Deployment for Simple Golang API 4 | 5 | ![goreport](https://goreportcard.com/badge/github.com/devopscorner/golang-deployment/src) 6 | ![all contributors](https://img.shields.io/github/contributors/devopscorner/golang-deployment) 7 | ![tags](https://img.shields.io/github/v/tag/devopscorner/golang-deployment?sort=semver) 8 | [![docker pulls](https://img.shields.io/docker/pulls/devopscorner/bookstore.svg)](https://hub.docker.com/r/devopscorner/bookstore/) 9 | ![download all](https://img.shields.io/github/downloads/devopscorner/golang-deployment/total.svg) 10 | ![view](https://views.whatilearened.today/views/github/devopscorner/golang-deployment.svg) 11 | ![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) 12 | ![issues](https://img.shields.io/github/issues/devopscorner/golang-deployment) 13 | ![pull requests](https://img.shields.io/github/issues-pr/devopscorner/golang-deployment) 14 | ![forks](https://img.shields.io/github/forks/devopscorner/golang-deployment) 15 | ![stars](https://img.shields.io/github/stars/devopscorner/golang-deployment) 16 | [![license](https://img.shields.io/github/license/devopscorner/golang-deployment)](https://img.shields.io/github/license/devopscorner/golang-deployment) 17 | 18 | --- 19 | 20 | ## Example CI/CD Script `cicd-openshift.yml` 21 | 22 | ``` 23 | apiVersion: build.openshift.io/v1 24 | kind: BuildConfig 25 | metadata: 26 | name: bookstore-build 27 | labels: 28 | app: bookstore 29 | spec: 30 | source: 31 | git: 32 | uri: https://github.com/devopscorner/golang-deployment.git 33 | contextDir: app 34 | strategy: 35 | dockerStrategy: 36 | dockerfilePath: Dockerfile 37 | type: Docker 38 | output: 39 | to: 40 | kind: ImageStreamTag 41 | name: devopscorner/bookstore:${IS_TAG} 42 | triggers: 43 | - type: ConfigChange 44 | - type: GitHub 45 | github: 46 | secret: my-secret 47 | - type: Generic 48 | generic: 49 | secret: my-secret 50 | - type: ImageChange 51 | imageChange: 52 | from: 53 | kind: ImageStreamTag 54 | name: devopscorner/bookstore:latest 55 | resources: 56 | limits: 57 | cpu: 1 58 | memory: 1Gi 59 | requests: 60 | cpu: 500m 61 | memory: 500Mi 62 | 63 | --- 64 | 65 | apiVersion: apps/v1 66 | kind: Deployment 67 | metadata: 68 | name: bookstore-deployment 69 | labels: 70 | app: bookstore 71 | spec: 72 | replicas: 1 73 | selector: 74 | matchLabels: 75 | app: bookstore 76 | template: 77 | metadata: 78 | labels: 79 | app: bookstore 80 | spec: 81 | containers: 82 | - name: bookstore 83 | image: devopscorner/bookstore:${IS_TAG} 84 | imagePullPolicy: Always 85 | ports: 86 | - containerPort: 8080 87 | imagePullSecrets: 88 | - name: my-registry-credentials 89 | 90 | --- 91 | 92 | apiVersion: v1 93 | kind: Service 94 | metadata: 95 | name: bookstore-service 96 | labels: 97 | app: bookstore 98 | spec: 99 | ports: 100 | - name: http 101 | port: 80 102 | targetPort: 8080 103 | selector: 104 | app: bookstore 105 | type: ClusterIP 106 | ``` -------------------------------------------------------------------------------- /docs/deployment-semaphoreci.md: -------------------------------------------------------------------------------- 1 | # Golang Deployment - Deployment with SemaphoreCI Pipeline 2 | 3 | Kubernetes Deployment for Simple Golang API 4 | 5 | ![goreport](https://goreportcard.com/badge/github.com/devopscorner/golang-deployment/src) 6 | ![all contributors](https://img.shields.io/github/contributors/devopscorner/golang-deployment) 7 | ![tags](https://img.shields.io/github/v/tag/devopscorner/golang-deployment?sort=semver) 8 | [![docker pulls](https://img.shields.io/docker/pulls/devopscorner/bookstore.svg)](https://hub.docker.com/r/devopscorner/bookstore/) 9 | ![download all](https://img.shields.io/github/downloads/devopscorner/golang-deployment/total.svg) 10 | ![view](https://views.whatilearened.today/views/github/devopscorner/golang-deployment.svg) 11 | ![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) 12 | ![issues](https://img.shields.io/github/issues/devopscorner/golang-deployment) 13 | ![pull requests](https://img.shields.io/github/issues-pr/devopscorner/golang-deployment) 14 | ![forks](https://img.shields.io/github/forks/devopscorner/golang-deployment) 15 | ![stars](https://img.shields.io/github/stars/devopscorner/golang-deployment) 16 | [![license](https://img.shields.io/github/license/devopscorner/golang-deployment)](https://img.shields.io/github/license/devopscorner/golang-deployment) 17 | 18 | --- 19 | 20 | ## Example CI/CD Script `cicd-semaphoreci.yml` 21 | 22 | ``` 23 | version: v1.0 24 | 25 | agent: 26 | machine: 27 | type: e1-standard-2 28 | 29 | blocks: 30 | - name: Build and Deploy 31 | task: 32 | jobs: 33 | - name: Build and Push Docker Image 34 | commands: 35 | - checkout 36 | - sem-version set 37 | - if [[ "$SEMAPHORE_BRANCH_NAME" == "main" ]]; then 38 | semver=1.0.0-${SEMAPHORE_GIT_SHA:0:8} 39 | elif [[ "$SEMAPHORE_BRANCH_NAME" == "features/"* ]]; then 40 | semver=1.0.0-${SEMAPHORE_BRANCH_NAME#features/}.${SEMAPHORE_GIT_SHA:0:8} 41 | elif [[ "$SEMAPHORE_BRANCH_NAME" == "bugfix/"* ]]; then 42 | semver=1.1.0-${SEMAPHORE_BRANCH_NAME#bugfix/}.${SEMAPHORE_GIT_SHA:0:8} 43 | elif [[ "$SEMAPHORE_BRANCH_NAME" == "hotfix/"* ]]; then 44 | semver=1.1.1-${SEMAPHORE_BRANCH_NAME#hotfix/}.${SEMAPHORE_GIT_SHA:0:8} 45 | fi 46 | - echo "Semantic version: $semver" 47 | - echo "imageTag=$semver" >> $SEMAPHORE_WORKSPACE/variables.env 48 | - sem-service aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com 49 | - docker build -t $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver . 50 | - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver 51 | - docker tag $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:latest 52 | - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:latest 53 | - name: Deploy to Kubernetes using Helmfile 54 | commands: 55 | - curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash 56 | - helmfile sync 57 | environment: 58 | AWS_REGION: ap-southeast-1 59 | AWS_ACCOUNT_ID: 0987612345 60 | IMAGE_NAME: devopscorner/bookstore 61 | ``` -------------------------------------------------------------------------------- /docs/deployment-spinnaker.md: -------------------------------------------------------------------------------- 1 | # Golang Deployment - Deployment with SemaphoreCI Pipeline 2 | 3 | Kubernetes Deployment for Simple Golang API 4 | 5 | ![goreport](https://goreportcard.com/badge/github.com/devopscorner/golang-deployment/src) 6 | ![all contributors](https://img.shields.io/github/contributors/devopscorner/golang-deployment) 7 | ![tags](https://img.shields.io/github/v/tag/devopscorner/golang-deployment?sort=semver) 8 | [![docker pulls](https://img.shields.io/docker/pulls/devopscorner/bookstore.svg)](https://hub.docker.com/r/devopscorner/bookstore/) 9 | ![download all](https://img.shields.io/github/downloads/devopscorner/golang-deployment/total.svg) 10 | ![view](https://views.whatilearened.today/views/github/devopscorner/golang-deployment.svg) 11 | ![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) 12 | ![issues](https://img.shields.io/github/issues/devopscorner/golang-deployment) 13 | ![pull requests](https://img.shields.io/github/issues-pr/devopscorner/golang-deployment) 14 | ![forks](https://img.shields.io/github/forks/devopscorner/golang-deployment) 15 | ![stars](https://img.shields.io/github/stars/devopscorner/golang-deployment) 16 | [![license](https://img.shields.io/github/license/devopscorner/golang-deployment)](https://img.shields.io/github/license/devopscorner/golang-deployment) 17 | 18 | --- 19 | 20 | ## Example CI/CD Script `cicd-semaphoreci.yml` 21 | 22 | ``` 23 | version: v1.0 24 | 25 | agent: 26 | machine: 27 | type: e1-standard-2 28 | 29 | blocks: 30 | - name: Build and Deploy 31 | task: 32 | jobs: 33 | - name: Build and Push Docker Image 34 | commands: 35 | - checkout 36 | - sem-version set 37 | - if [[ "$SEMAPHORE_BRANCH_NAME" == "main" ]]; then 38 | semver=1.0.0-${SEMAPHORE_GIT_SHA:0:8} 39 | elif [[ "$SEMAPHORE_BRANCH_NAME" == "features/"* ]]; then 40 | semver=1.0.0-${SEMAPHORE_BRANCH_NAME#features/}.${SEMAPHORE_GIT_SHA:0:8} 41 | elif [[ "$SEMAPHORE_BRANCH_NAME" == "bugfix/"* ]]; then 42 | semver=1.1.0-${SEMAPHORE_BRANCH_NAME#bugfix/}.${SEMAPHORE_GIT_SHA:0:8} 43 | elif [[ "$SEMAPHORE_BRANCH_NAME" == "hotfix/"* ]]; then 44 | semver=1.1.1-${SEMAPHORE_BRANCH_NAME#hotfix/}.${SEMAPHORE_GIT_SHA:0:8} 45 | fi 46 | - echo "Semantic version: $semver" 47 | - echo "imageTag=$semver" >> $SEMAPHORE_WORKSPACE/variables.env 48 | - sem-service aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com 49 | - docker build -t $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver . 50 | - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver 51 | - docker tag $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:latest 52 | - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:latest 53 | - name: Deploy to Kubernetes using Helmfile 54 | commands: 55 | - curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash 56 | - helmfile sync 57 | environment: 58 | AWS_REGION: ap-southeast-1 59 | AWS_ACCOUNT_ID: 0987612345 60 | IMAGE_NAME: devopscorner/bookstore 61 | ``` -------------------------------------------------------------------------------- /docs/deployment-travisci.md: -------------------------------------------------------------------------------- 1 | # Golang Deployment - Deployment with TravisCI Pipeline 2 | 3 | Kubernetes Deployment for Simple Golang API 4 | 5 | ![goreport](https://goreportcard.com/badge/github.com/devopscorner/golang-deployment/src) 6 | ![all contributors](https://img.shields.io/github/contributors/devopscorner/golang-deployment) 7 | ![tags](https://img.shields.io/github/v/tag/devopscorner/golang-deployment?sort=semver) 8 | [![docker pulls](https://img.shields.io/docker/pulls/devopscorner/bookstore.svg)](https://hub.docker.com/r/devopscorner/bookstore/) 9 | ![download all](https://img.shields.io/github/downloads/devopscorner/golang-deployment/total.svg) 10 | ![view](https://views.whatilearened.today/views/github/devopscorner/golang-deployment.svg) 11 | ![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) 12 | ![issues](https://img.shields.io/github/issues/devopscorner/golang-deployment) 13 | ![pull requests](https://img.shields.io/github/issues-pr/devopscorner/golang-deployment) 14 | ![forks](https://img.shields.io/github/forks/devopscorner/golang-deployment) 15 | ![stars](https://img.shields.io/github/stars/devopscorner/golang-deployment) 16 | [![license](https://img.shields.io/github/license/devopscorner/golang-deployment)](https://img.shields.io/github/license/devopscorner/golang-deployment) 17 | 18 | --- 19 | 20 | ## Example CI/CD Script `cicd-travisci.yml` 21 | 22 | ``` 23 | language: go 24 | 25 | services: 26 | - docker 27 | 28 | env: 29 | global: 30 | - AWS_REGION=ap-southeast-1 31 | - AWS_ACCOUNT_ID=0987612345 32 | - IMAGE_NAME=devopscorner/bookstore 33 | 34 | before_script: 35 | - curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 36 | - dep ensure 37 | 38 | script: 39 | - if [[ "$TRAVIS_BRANCH" == "main" ]]; then 40 | semver=1.0.0-${TRAVIS_COMMIT:0:8}; 41 | elif [[ "$TRAVIS_BRANCH" == "features/"* ]]; then 42 | semver=1.0.0-${TRAVIS_BRANCH#features/}.${TRAVIS_COMMIT:0:8}; 43 | elif [[ "$TRAVIS_BRANCH" == "bugfix/"* ]]; then 44 | semver=1.1.0-${TRAVIS_BRANCH#bugfix/}.${TRAVIS_COMMIT:0:8}; 45 | elif [[ "$TRAVIS_BRANCH" == "hotfix/"* ]]; then 46 | semver=1.1.1-${TRAVIS_BRANCH#hotfix/}.${TRAVIS_COMMIT:0:8}; 47 | fi 48 | - echo "Semantic version: $semver" 49 | - echo "imageTag=$semver" >> $HOME/variables.env 50 | - docker build -t $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver . 51 | - docker tag $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:latest 52 | - aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com 53 | - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:$semver 54 | - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_NAME:latest 55 | 56 | after_success: 57 | - curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash 58 | - helmfile sync 59 | 60 | branches: 61 | only: 62 | - main 63 | - /^features\/.*$/ 64 | - /^bugfix\/.*$/ 65 | - /^hotfix\/.*$/ 66 | ``` -------------------------------------------------------------------------------- /docs/gitops-devsecops-flow-azure.md: -------------------------------------------------------------------------------- 1 | # Golang Deployment - GitOps & GitOps DevSecOps Flow 2 | 3 | Kubernetes Deployment for Simple Golang API 4 | 5 | ![goreport](https://goreportcard.com/badge/github.com/devopscorner/golang-deployment/src) 6 | ![all contributors](https://img.shields.io/github/contributors/devopscorner/golang-deployment) 7 | ![tags](https://img.shields.io/github/v/tag/devopscorner/golang-deployment?sort=semver) 8 | [![docker pulls](https://img.shields.io/docker/pulls/devopscorner/bookstore.svg)](https://hub.docker.com/r/devopscorner/bookstore/) 9 | ![download all](https://img.shields.io/github/downloads/devopscorner/golang-deployment/total.svg) 10 | ![view](https://views.whatilearened.today/views/github/devopscorner/golang-deployment.svg) 11 | ![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) 12 | ![issues](https://img.shields.io/github/issues/devopscorner/golang-deployment) 13 | ![pull requests](https://img.shields.io/github/issues-pr/devopscorner/golang-deployment) 14 | ![forks](https://img.shields.io/github/forks/devopscorner/golang-deployment) 15 | ![stars](https://img.shields.io/github/stars/devopscorner/golang-deployment) 16 | [![license](https://img.shields.io/github/license/devopscorner/golang-deployment)](https://img.shields.io/github/license/devopscorner/golang-deployment) 17 | 18 | --- 19 | 20 | ## GitOps Flow 21 | 22 | - Azure DevOps Pipeline 23 | ![Azure DevOps Pipeline](./assets/gitops-flow-azure.png) 24 | 25 | ## GitOps DevSecOps Flow 26 | 27 | - Azure DevSecOps Pipeline 28 | ![Azure DevSecOps Pipeline](./assets/gitops-devsecops-azure.png) 29 | 30 | - Synchronize Repository from Azure DevOps Repo to AWS CodeCommit 31 | - Build images using AWS CodeBuild from AWS CodeCommit 32 | - Running Deployment with AWS CodePipeline 33 | -------------------------------------------------------------------------------- /docs/test-grpc-api.md: -------------------------------------------------------------------------------- 1 | # Golang Deployment - Test gRPC API 2 | 3 | Kubernetes Deployment for Simple Golang API 4 | 5 | ![goreport](https://goreportcard.com/badge/github.com/devopscorner/golang-deployment/src) 6 | ![all contributors](https://img.shields.io/github/contributors/devopscorner/golang-deployment) 7 | ![tags](https://img.shields.io/github/v/tag/devopscorner/golang-deployment?sort=semver) 8 | [![docker pulls](https://img.shields.io/docker/pulls/devopscorner/bookstore.svg)](https://hub.docker.com/r/devopscorner/bookstore/) 9 | ![download all](https://img.shields.io/github/downloads/devopscorner/golang-deployment/total.svg) 10 | ![view](https://views.whatilearened.today/views/github/devopscorner/golang-deployment.svg) 11 | ![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) 12 | ![issues](https://img.shields.io/github/issues/devopscorner/golang-deployment) 13 | ![pull requests](https://img.shields.io/github/issues-pr/devopscorner/golang-deployment) 14 | ![forks](https://img.shields.io/github/forks/devopscorner/golang-deployment) 15 | ![stars](https://img.shields.io/github/stars/devopscorner/golang-deployment) 16 | [![license](https://img.shields.io/github/license/devopscorner/golang-deployment)](https://img.shields.io/github/license/devopscorner/golang-deployment) 17 | 18 | --- 19 | 20 | ## Development 21 | 22 | ### Prequests 23 | 24 | - Install jq libraries 25 | 26 | ``` 27 | apt-get install -y jq 28 | ``` 29 | 30 | - Install golang dependencies 31 | 32 | ``` 33 | cd src 34 | go mod init 35 | go mod tidy 36 | ``` 37 | 38 | ### Runnning 39 | 40 | ``` 41 | go run main.go 42 | ``` 43 | 44 | ### Runnning Test 45 | 46 | ``` 47 | go run main_test.go 48 | ``` -------------------------------------------------------------------------------- /docs/workflow-cicd-bookstore-pipeline.md: -------------------------------------------------------------------------------- 1 | # Golang Deployment 2 | 3 | Kubernetes Deployment for Simple Golang API 4 | 5 | ![goreport](https://goreportcard.com/badge/github.com/devopscorner/golang-deployment/src) 6 | ![all contributors](https://img.shields.io/github/contributors/devopscorner/golang-deployment) 7 | ![tags](https://img.shields.io/github/v/tag/devopscorner/golang-deployment?sort=semver) 8 | [![docker pulls](https://img.shields.io/docker/pulls/devopscorner/bookstore.svg)](https://hub.docker.com/r/devopscorner/bookstore/) 9 | ![download all](https://img.shields.io/github/downloads/devopscorner/golang-deployment/total.svg) 10 | ![view](https://views.whatilearened.today/views/github/devopscorner/golang-deployment.svg) 11 | ![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) 12 | ![issues](https://img.shields.io/github/issues/devopscorner/golang-deployment) 13 | ![pull requests](https://img.shields.io/github/issues-pr/devopscorner/golang-deployment) 14 | ![forks](https://img.shields.io/github/forks/devopscorner/golang-deployment) 15 | ![stars](https://img.shields.io/github/stars/devopscorner/golang-deployment) 16 | [![license](https://img.shields.io/github/license/devopscorner/golang-deployment)](https://img.shields.io/github/license/devopscorner/golang-deployment) 17 | 18 | --- 19 | 20 | ## Workflow CI/CD Pipeline 21 | 22 | - Create Container Image CI/CD CodeBuild, refer to this repository: [DevOpsCorner CI/CD CodeBuild](https://github.com/devopscorner/devopscorner-container/tree/main/compose/docker/cicd-codebuild) 23 | 24 | - Create HelmChart Template Global, go to [this](https://github.com/devopscorner/devopscorner-helm) section 25 | - `api` 26 | - `backend` 27 | - `frontend` 28 | - `stateful` 29 | - `secretref` 30 | - `svcrole` 31 | - Deploy HelmChart Template Global to S3 32 | - `AWS_REGION=ap-southeast-1 helm repo add devopscorner-lab s3://devopscorner-helm-chart/lab` 33 | - `AWS_REGION=ap-southeast-1 helm repo add devopscorner-staging s3://devopscorner-helm-chart/staging` 34 | - `AWS_REGION=ap-southeast-1 helm repo add devopscorner-prod s3://devopscorner-helm-chart/prod` 35 | - Create HelmChart Template for GO App 36 | - Helm template GO App (`_infra/{env}/helm-template.yml`) 37 | - Helm value GO App (`_infra/{env}/helm-value.yml`) 38 | 39 | - Create `Dockerfile` for Container GO App CI/CD 40 | - Create Script CI/CD 41 | - `ecr-build.sh` 42 | - `ecr-push.sh` 43 | - `ecr-tag.sh` 44 | - `git-clone.sh` 45 | - `Makefile` 46 | 47 | - Register Container Image GO App to Amazon ECR (Container Registry) 48 | - Create script for Building Container Image GO App (`.aws/buildspec-build.yml`) 49 | - Create script for Deployment Container Image GO App to EKS (`.aws/buildspec-deploy.yml`) 50 | - Setup Variable Environment / Using Config Secret with **AWS Systems Manager (Parameter Store)** 51 | 52 | - Create Pipeline with AWS CodePipeline 53 | - **Source**: Reference from CodeCommit and/or 3rd Party Repository (GitHub, GitLab, BitBucket, Azure DevOps) 54 | - **Build**: Building Container GO App 55 | - Golang Unit Test 56 | - Code Quality and Code Security 57 | - Build Container GO App 58 | - Tagging Container GO App 59 | - Push Container GO App to Container Registry (ECR / Dockerhub) 60 | - **Deploy**: Deploy Container GO App 61 | - **Static Application Security Testing (SAST)**, or static analysis 62 | - Manual Approval 63 | - **Deploy-DEV** 64 | - Manual Approval 65 | - **Deploy-UAT** 66 | - **Dynamic Application Security Testing (DAST)** 67 | - Manual Approval 68 | - **Deploy-PROD** 69 | 70 | - Running Deployment: Commit -> Push -> Webhook (CodeCommit) 71 | 72 | - CodeBuild Process 73 | - Build Container 74 | - Environment Image: `aws/codebuild/amazonlinux2-x86_64-standard:4.0` 75 | - Environment Type: `Linux` 76 | - Buildspec: `.aws/buildspec-build.yml` 77 | - Deploy Container 78 | - Environment Image: `devopscorner/cicd:codebuild-4.0` or `YOUR_AWS_ACCOUNT.dkr.ecr.ap-southeast-1.amazonaws.com/devopscorner/cicd:codebuild-4.0` 79 | - Environment Type: `Linux` 80 | - Buildspec: `.aws/buildspec-deploy.yml` 81 | -------------------------------------------------------------------------------- /ecr-build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # ----------------------------------------------------------------------------- 3 | # Docker Build Container (Elastic Container Registry - ECR) 4 | # ----------------------------------------------------------------------------- 5 | # Author : Dwi Fahni Denni 6 | # License : Apache v2 7 | # ----------------------------------------------------------------------------- 8 | set -e 9 | 10 | export AWS_ACCOUNT_ID=$1 11 | export AWS_DEFAULT_REGION="ap-southeast-1" 12 | export CI_REGISTRY="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com" 13 | export CI_ECR_PATH=$3 14 | 15 | export IMAGE="$CI_REGISTRY/$CI_ECR_PATH" 16 | 17 | docker_build() { 18 | export FILE=$2 19 | export BASE_IMAGE=$4 20 | export TAGS_ID=$5 21 | export CUSTOM_TAGS=$6 22 | 23 | if [ "$CUSTOM_TAGS" = "" ]; then 24 | echo "Build Image => $IMAGE:$BASE_IMAGE" 25 | echo ">> docker build -t $IMAGE:$BASE_IMAGE -f $FILE ." 26 | docker build -t $IMAGE:$BASE_IMAGE -f $FILE . 27 | echo '---' 28 | 29 | echo "Build Image => $IMAGE:$TAGS_ID" 30 | echo ">> docker build -t $IMAGE:$TAGS_ID -f $FILE ." 31 | docker build -t $IMAGE:$TAGS_ID -f $FILE . 32 | echo '---' 33 | 34 | echo "Build Image => $IMAGE:$BASE_IMAGE-$TAGS_ID" 35 | echo ">> docker build -t $IMAGE:$BASE_IMAGE-$TAGS_ID -f $FILE ." 36 | docker build -t $IMAGE:$BASE_IMAGE-$TAGS_ID -f $FILE . 37 | echo '---' 38 | else 39 | echo "Build Image => $IMAGE:$BASE_IMAGE" 40 | echo ">> docker build -t $IMAGE:$BASE_IMAGE -f $FILE ." 41 | docker build -t $IMAGE:$BASE_IMAGE -f $FILE . 42 | echo '---' 43 | 44 | echo "Build Image => $IMAGE:$TAGS_ID" 45 | echo "docker build -t $IMAGE:$TAGS_ID -f $FILE ." 46 | docker build -t $IMAGE:$TAGS_ID -f $FILE . 47 | echo '---' 48 | 49 | echo "Build Image => $IMAGE:$BASE_IMAGE-$TAGS_ID" 50 | echo ">> docker build -t $IMAGE:$BASE_IMAGE-$TAGS_ID -f $FILE ." 51 | docker build -t $IMAGE:$BASE_IMAGE-$TAGS_ID -f $FILE . 52 | echo '---' 53 | 54 | echo "Build Image => $IMAGE:$TAGS_ID-${CUSTOM_TAGS}" 55 | docker build -t $IMAGE:$TAGS_ID-${CUSTOM_TAGS} -f $FILE . 56 | echo ">> docker build -t $IMAGE:$TAGS_ID-${CUSTOM_TAGS} -f $FILE ." 57 | echo '---' 58 | 59 | echo "Build Image => $IMAGE:$BASE_IMAGE-$TAGS_ID-${CUSTOM_TAGS}" 60 | echo ">> docker build -t $IMAGE:$BASE_IMAGE-$TAGS_ID-${CUSTOM_TAGS} -f $FILE ." 61 | docker build -t $IMAGE:$BASE_IMAGE-$TAGS_ID-${CUSTOM_TAGS} -f $FILE . 62 | echo '---' 63 | fi 64 | } 65 | 66 | main() { 67 | # docker_build 0987654321 Dockerfile devopscorner/bookstore alpine [version|latest|tags] [custom-tags] 68 | # docker_build 0987654321 Dockerfile.alpine-3.15 devopscorner/bookstore alpine [version|latest|tags] [custom-tags] 69 | # docker_build 0987654321 Dockerfile.alpine-3.16 devopscorner/bookstore alpine [version|latest|tags] [custom-tags] 70 | docker_build $1 $2 $3 $4 $5 $6 71 | echo '' 72 | echo '-- ALL DONE --' 73 | } 74 | 75 | ### START HERE ### 76 | main $1 $2 $3 $4 $5 $6 77 | 78 | ### How to Execute ### 79 | # ./ecr-build.sh [AWS_ACCOUNT] Dockerfile [ECR_PATH] [alpine] [version|latest|tags] [custom-tags] 80 | -------------------------------------------------------------------------------- /ecr-pull.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # ----------------------------------------------------------------------------- 3 | # Docker Pull Container 4 | # ----------------------------------------------------------------------------- 5 | # Author : Dwi Fahni Denni 6 | # License : Apache v2 7 | # ----------------------------------------------------------------------------- 8 | set -e 9 | 10 | export AWS_ACCOUNT_ID=$1 11 | export AWS_DEFAULT_REGION="ap-southeast-1" 12 | export CI_REGISTRY="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com" 13 | export CI_ECR_PATH=$2 14 | 15 | export IMAGE="$CI_REGISTRY/$CI_ECR_PATH" 16 | 17 | # export CICD_VERSION="1.23" 18 | # export ALPINE_VERSION="3.16" 19 | # export UBUNTU_VERSION="22.04" 20 | # export CODEBUILD_VERSION="4.0" 21 | 22 | login_ecr() { 23 | echo "=============" 24 | echo " Login ECR " 25 | echo "=============" 26 | PASSWORD=$(aws ecr get-login-password --region $AWS_DEFAULT_REGION) 27 | echo $PASSWORD | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com 28 | echo '- DONE -' 29 | echo '' 30 | } 31 | 32 | docker_pull() { 33 | export TAGS_ID=$2 34 | echo "Docker Pull => $IMAGE:$TAGS_ID" 35 | echo ">> docker pull $IMAGE:$TAGS_ID" 36 | docker pull $IMAGE:$TAGS_ID 37 | echo '- DONE -' 38 | echo '' 39 | } 40 | 41 | main() { 42 | login_ecr 43 | # docker_pull 0987654321 devopscorner/bookstore alpine 44 | docker_pull $AWS_ACCOUNT_ID $2 $3 45 | echo '' 46 | echo '-- ALL DONE --' 47 | } 48 | 49 | ### START HERE ### 50 | main $1 $2 $3 51 | 52 | ### How to Execute ### 53 | # ./ecr-pull.sh [AWS_ACCOUNT] [ECR_PATH] [alpine|version|latest|tags|custom-tags] -------------------------------------------------------------------------------- /ecr-push.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # ----------------------------------------------------------------------------- 3 | # Docker Push Container (Elastic Container Registry - ECR) 4 | # ----------------------------------------------------------------------------- 5 | # Author : Dwi Fahni Denni 6 | # License : Apache v2 7 | # ----------------------------------------------------------------------------- 8 | set -e 9 | 10 | export AWS_ACCOUNT_ID=$1 11 | export AWS_DEFAULT_REGION="ap-southeast-1" 12 | export CI_REGISTRY="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com" 13 | export CI_ECR_PATH=$2 14 | 15 | export IMAGE="$CI_REGISTRY/$CI_ECR_PATH" 16 | 17 | login_ecr() { 18 | echo "=============" 19 | echo " Login ECR " 20 | echo "=============" 21 | PASSWORD=$(aws ecr get-login-password --region $AWS_DEFAULT_REGION) 22 | echo $PASSWORD | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com 23 | echo '- DONE -' 24 | echo '' 25 | } 26 | 27 | docker_push() { 28 | export TAGS_ID=$3 29 | IMAGES=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep $IMAGE:${TAGS_ID}) 30 | for IMG in $IMAGES; do 31 | echo "Docker Push => $IMG" 32 | echo ">> docker push $IMG" 33 | docker push $IMG 34 | echo '- DONE -' 35 | echo '' 36 | done 37 | } 38 | 39 | main() { 40 | login_ecr 41 | # docker_push 0987654321 devopscorner/bookstore [alpine|version|latest|tags|custom-tags] 42 | docker_push $1 $2 $3 43 | echo '' 44 | echo '-- ALL DONE --' 45 | } 46 | 47 | ### START HERE ### 48 | main $1 $2 $3 49 | 50 | ### How to Execute ### 51 | # ./ecr-push.sh [AWS_ACCOUNT] [ECR_PATH] [alpine|latest|tags|custom-tags] 52 | -------------------------------------------------------------------------------- /ecr-tag.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # ----------------------------------------------------------------------------- 3 | # Docker Tag Container (Elastic Container Registry - ECR) 4 | # ----------------------------------------------------------------------------- 5 | # Author : Dwi Fahni Denni 6 | # License : Apache v2 7 | # ----------------------------------------------------------------------------- 8 | set -e 9 | 10 | export AWS_ACCOUNT_ID=$1 11 | export AWS_DEFAULT_REGION="ap-southeast-1" 12 | export CI_REGISTRY="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com" 13 | export CI_ECR_PATH=$2 14 | 15 | export IMAGE="$CI_REGISTRY/$CI_ECR_PATH" 16 | 17 | set_tag() { 18 | export BASE_IMAGE=$3 19 | export TAGS_ID=$4 20 | export CUSTOM_TAGS=$5 21 | export COMMIT_HASH=$(git log -1 --format=format:"%H") 22 | 23 | if [ "$CUSTOM_TAGS" = "" ]; then 24 | export TAGS="$TAGS_ID \ 25 | $BASE_IMAGE-$TAGS_ID \ 26 | $TAGS_ID-$COMMIT_HASH \ 27 | $BASE_IMAGE-$COMMIT_HASH " 28 | else 29 | export TAGS="$TAGS_ID \ 30 | $BASE_IMAGE-$TAGS_ID \ 31 | $TAGS_ID-$COMMIT_HASH \ 32 | $BASE_IMAGE-$COMMIT_HASH \ 33 | $TAGS_ID-$CUSTOM_TAGS" 34 | fi 35 | } 36 | 37 | docker_tag() { 38 | for TAG in $TAGS; do 39 | echo "Docker Tags => $IMAGE:$TAG" 40 | echo ">> docker tag $IMAGE:$BASE_IMAGE $IMAGE:$TAG" 41 | docker tag $IMAGE:$BASE_IMAGE $IMAGE:$TAG 42 | echo '- DONE -' 43 | echo '' 44 | done 45 | } 46 | 47 | main() { 48 | # set_tag 0987654321 devopscorner/bookstore alpine [version|latest|tags] [custom-tags] 49 | set_tag $1 $2 $3 $4 $5 50 | docker_tag 51 | echo '' 52 | echo '-- ALL DONE --' 53 | } 54 | 55 | ### START HERE ### 56 | main $1 $2 $3 $4 $5 57 | 58 | ### How to Execute ### 59 | # ./ecr-tag.sh [AWS_ACCOUNT] [ECR_PATH] [alpine|codebuild] [version|latest|tags] [custom-tags] 60 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ash 2 | 3 | export ALPINE_VERSION=3.17 4 | export GIN_MODE=release 5 | export APP_URL=http://localhost 6 | export APP_PORT=8080 7 | export DB_CONNECTION=sqlite 8 | export DB_REGION=ap-southeast-1 9 | export DB_HOST=localhost 10 | export DB_PORT= 11 | export DB_DATABASE=go-bookstore.db 12 | export DB_USERNAME=root 13 | export DB_PASSWORD= 14 | export JWT_AUTH_USERNAME=devopscorner 15 | export JWT_AUTH_PASSWORD=DevOpsCorner@2023 16 | export JWT_SECRET=s3cr3t 17 | 18 | /go/goapp -------------------------------------------------------------------------------- /git-clone.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # ----------------------------------------------------------------------------- 3 | # Clone Repository 4 | # ----------------------------------------------------------------------------- 5 | # Author : Dwi Fahni Denni 6 | # License : Apache v2 7 | # ----------------------------------------------------------------------------- 8 | set -e 9 | 10 | GIT_SSH_COMMAND='ssh -i ~/.ssh/id_rsa -o IdentitiesOnly=yes -F /dev/null' git clone --depth 5 $1 $2 11 | -------------------------------------------------------------------------------- /run-docker.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # ----------------------------------------------------------------------------- 3 | # Docker Build Container 4 | # ----------------------------------------------------------------------------- 5 | # Author : Dwi Fahni Denni (@zeroc0d3) 6 | # License : Apache v2 7 | # ----------------------------------------------------------------------------- 8 | set -e 9 | 10 | TITLE="DOCKER BUILD CONTAINER SCRIPT" # script name 11 | VER="3.1" # script version 12 | ENV="0" # container environment (0 = development, 1 = staging, 2 = production) 13 | SKIP_BUILD="0" # (0 = with build process, 1 = bypass build process) 14 | REMOVE_CACHE="0" # (0 = using cache, 1 = no-cache) 15 | RECREATE_CONTAINER="0" # (0 = disable recreate container, 1 = force recreate container) 16 | DAEMON_MODE="1" # (0 = disable daemon mode, 1 = running daemon mode / background) 17 | CHMOD_DATA="0" # (0 = disable chmod 777 folder data, 1 = skip chmod 777 folder data) 18 | 19 | USERNAME=$(echo $USER) 20 | PATH_HOME=$(echo $HOME) 21 | 22 | PATH_APP=$(pwd) 23 | PATH_CONTAINER_DATA="$PATH_APP/data" 24 | COMPOSE_PATH="$PATH_APP" 25 | COMPOSE_APP="$COMPOSE_PATH/docker-compose.yml" 26 | 27 | COL_RED="\033[22;31m" 28 | COL_GREEN="\033[22;32m" 29 | COL_BLUE="\033[22;34m" 30 | COL_END="\033[0m" 31 | 32 | export RUBY_VERSION="2.7.1" 33 | 34 | get_time() { 35 | DATE=$(date '+%Y-%m-%d %H:%M:%S') 36 | } 37 | 38 | print_line0() { 39 | echo "$COL_GREEN=====================================================================================$COL_END" 40 | } 41 | 42 | print_line1() { 43 | echo "$COL_GREEN-------------------------------------------------------------------------------------$COL_END" 44 | } 45 | 46 | print_line2() { 47 | echo "-------------------------------------------------------------------------------------" 48 | } 49 | 50 | logo() { 51 | clear 52 | print_line0 53 | echo "$COL_RED'########:'########:'########:::'#######:::'######::::'#####:::'########:::'#######:: $COL_END" 54 | echo "$COL_RED..... ##:: ##.....:: ##.... ##:'##.... ##:'##... ##::'##.. ##:: ##.... ##:'##.... ##: $COL_END" 55 | echo "$COL_RED:::: ##::: ##::::::: ##:::: ##: ##:::: ##: ##:::..::'##:::: ##: ##:::: ##:..::::: ##: $COL_END" 56 | echo "$COL_RED::: ##:::: ######::: ########:: ##:::: ##: ##::::::: ##:::: ##: ##:::: ##::'#######:: $COL_END" 57 | echo "$COL_RED:: ##::::: ##...:::: ##.. ##::: ##:::: ##: ##::::::: ##:::: ##: ##:::: ##::...... ##: $COL_END" 58 | echo "$COL_RED: ##:::::: ##::::::: ##::. ##:: ##:::: ##: ##::: ##:. ##:: ##:: ##:::: ##:'##:::: ##: $COL_END" 59 | echo "$COL_RED ########: ########: ##:::. ##:. #######::. ######:::. #####::: ########::. #######:: $COL_END" 60 | echo "$COL_RED........::........::..:::::..:::.......::::......:::::.....::::........::::.......::: $COL_END" 61 | print_line1 62 | echo "$COL_GREEN# $TITLE :: ver-$VER $COL_END" 63 | } 64 | 65 | header() { 66 | logo 67 | print_line0 68 | get_time 69 | echo "$COL_RED# BEGIN PROCESS..... (Please Wait) $COL_END" 70 | echo "$COL_RED# Start at: $DATE $COL_END\n" 71 | } 72 | 73 | footer() { 74 | print_line0 75 | get_time 76 | echo "$COL_RED# Finish at: $DATE $COL_END" 77 | echo "$COL_RED# END PROCESS..... $COL_END\n" 78 | } 79 | 80 | chmod_data() { 81 | if [ "$CHMOD_DATA" = "1" ]; then 82 | sudo chmod 777 -R $PATH_CONTAINER_DATA 83 | fi 84 | } 85 | 86 | build_env() { 87 | if [ "$ENV" = "0" ]; then 88 | BUILD_ENV="$CONTAINER_DEVELOPMENT" 89 | elif [ "$ENV" = "1" ]; then 90 | BUILD_ENV="$CONTAINER_STAGING" 91 | else 92 | BUILD_ENV="$CONTAINER_PRODUCTION" 93 | fi 94 | } 95 | 96 | cache() { 97 | if [ "$REMOVE_CACHE" = "0" ]; then 98 | CACHE="" 99 | else 100 | CACHE="--no-cache " 101 | fi 102 | } 103 | 104 | recreate() { 105 | if [ "$RECREATE_CONTAINER" = "0" ]; then 106 | RECREATE="" 107 | else 108 | RECREATE="--force-recreate " 109 | fi 110 | } 111 | 112 | daemon_mode() { 113 | if [ "$DAEMON_MODE" = "0" ]; then 114 | DAEMON="" 115 | else 116 | DAEMON="-d " 117 | fi 118 | } 119 | 120 | prepare_volume() { 121 | print_line2 122 | 123 | VOL_PATH="/opt/data/docker" 124 | 125 | # VOL_CONTAINER="$VOL_PATH/airflow/dags \ 126 | # $VOL_PATH/airflow/logs \ 127 | # $VOL_PATH/airflow/plugins \ 128 | # $VOL_PATH/postgresql14/pgdata \ 129 | # $VOL_PATH/portainer" 130 | 131 | get_time 132 | echo "$COL_BLUE[ $DATE ] ##### Prepare Volume Container: $COL_END" 133 | echo "$COL_GREEN[ $DATE ] sudo mkdir -p [vol_docker]\n" 134 | 135 | # for VOL in $VOL_CONTAINER; do 136 | # get_time 137 | # print_line2 138 | # echo "$COL_GREEN[ $DATE ] sudo mkdir -p $VOL $COL_END" 139 | # print_line2 140 | # sudo mkdir -p $VOL 141 | # echo '- DONE -' 142 | # done 143 | 144 | sudo chmod -R 777 $VOL_PATH 145 | echo "-- VOLUME DONE --" 146 | echo '' 147 | } 148 | 149 | docker_build() { 150 | if [ "$SKIP_BUILD" = "0" ]; then 151 | print_line2 152 | get_time 153 | echo "$COL_BLUE[ $DATE ] ##### Docker Compose Build: $COL_END" 154 | echo "$COL_GREEN[ $DATE ] docker-compose -f $COMPOSE_APP build $CACHE$BUILD_ENV $COL_END\n" 155 | 156 | for CONTAINER in $BUILD_ENV; do 157 | get_time 158 | print_line2 159 | echo "$COL_GREEN[ $DATE ] docker-compose -f $COMPOSE_APP build $CONTAINER $COL_END" 160 | print_line2 161 | docker-compose -f $COMPOSE_APP build $CONTAINER 162 | echo '- DONE -' 163 | echo '' 164 | done 165 | fi 166 | echo "-- BUILD DONE --" 167 | echo '' 168 | } 169 | 170 | docker_up() { 171 | daemon_mode 172 | print_line2 173 | get_time 174 | echo "$COL_BLUE[ $DATE ] ##### Docker Compose Up: $COL_END" 175 | echo "$COL_GREEN[ $DATE ] docker-compose -f $COMPOSE_APP up $DAEMON $RECREATE$BUILD_ENV $COL_END\n" 176 | get_time 177 | print_line2 178 | echo "$COL_GREEN[ $DATE ] docker-compose -f $COMPOSE_APP up $DAEMON $RECREATE$BUILD_ENV $COL_END" 179 | print_line2 180 | docker-compose -f $COMPOSE_APP up $DAEMON $RECREATE$BUILD_ENV 181 | echo "-- UP DONE --" 182 | echo '' 183 | } 184 | 185 | main() { 186 | header 187 | cache 188 | recreate 189 | build_env 190 | #prepare_volume 191 | docker_build 192 | docker_up 193 | footer 194 | # chmod_data 195 | } 196 | 197 | ### START HERE ### 198 | main $@ 199 | -------------------------------------------------------------------------------- /src/.env.example: -------------------------------------------------------------------------------- 1 | GIN_MODE=release 2 | APP_URL=http://localhost 3 | APP_PORT=8080 4 | DB_CONNECTION=sqlite 5 | DB_REGION=ap-southeast-1 6 | DB_HOST=localhost 7 | DB_PORT= 8 | DB_DATABASE=go-bookstore.db 9 | DB_USERNAME=root 10 | DB_PASSWORD= 11 | JWT_AUTH_USERNAME=devopscorner 12 | JWT_AUTH_PASSWORD=DevOpsCorner@2023 13 | JWT_SECRET=s3cr3t 14 | -------------------------------------------------------------------------------- /src/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/spf13/viper" 5 | ) 6 | 7 | type Config struct { 8 | AppUrl string 9 | AppPort string 10 | DbConnection string 11 | DbRegion string 12 | DbHost string 13 | DbPort string 14 | DbDatabase string 15 | DbUsername string 16 | DbPassword string 17 | JwtAuthUsername string 18 | JwtAuthPassword string 19 | JwtIssuer string 20 | JwtSecret string 21 | } 22 | 23 | func LoadConfig() (*Config, error) { 24 | viper.SetConfigFile(".env") 25 | viper.SetConfigType("env") 26 | viper.AutomaticEnv() 27 | 28 | if err := viper.ReadInConfig(); err != nil { 29 | return nil, err 30 | } 31 | 32 | viper.SetDefault("APP_URL", "http://localhost") 33 | viper.SetDefault("APP_PORT", "8080") 34 | viper.SetDefault("DB_CONNECTION", "sqlite") 35 | viper.SetDefault("DB_REGION", "ap-southeast-1") 36 | viper.SetDefault("DB_HOST", "localhost") 37 | viper.SetDefault("DB_PORT", "") 38 | viper.SetDefault("DB_DATABASE", "go-bookstore") 39 | viper.SetDefault("DB_USERNAME", "root") 40 | viper.SetDefault("DB_PASSWORD", "") 41 | viper.SetDefault("JWT_AUTH_USERNAME", "devopscorner") 42 | viper.SetDefault("JWT_AUTH_PASSWORD", "DevOpsCorner@2023") 43 | viper.SetDefault("JWT_SECRET", "s3cr3t") 44 | 45 | config := &Config{ 46 | AppUrl: viper.GetString("APP_URL"), 47 | AppPort: viper.GetString("APP_PORT"), 48 | DbConnection: viper.GetString("DB_CONNECTION"), 49 | DbRegion: viper.GetString("DB_REGION"), 50 | DbHost: viper.GetString("DB_HOST"), 51 | DbPort: viper.GetString("DB_PORT"), 52 | DbDatabase: viper.GetString("DB_DATABASE"), 53 | DbUsername: viper.GetString("DB_USERNAME"), 54 | DbPassword: viper.GetString("DB_PASSWORD"), 55 | JwtAuthUsername: viper.GetString("JWT_AUTH_USERNAME"), 56 | JwtAuthPassword: viper.GetString("JWT_AUTH_PASSWORD"), 57 | JwtSecret: viper.GetString("JWT_SECRET"), 58 | } 59 | 60 | return config, nil 61 | } 62 | 63 | func AppUrl() string { 64 | config := &Config{ 65 | AppUrl: viper.GetString("APP_URL"), 66 | } 67 | return config.AppUrl 68 | } 69 | 70 | func AppPort() string { 71 | config := &Config{ 72 | AppPort: viper.GetString("APP_PORT"), 73 | } 74 | return config.AppPort 75 | } 76 | 77 | func DbConnection() string { 78 | config := &Config{ 79 | DbConnection: viper.GetString("DB_CONNECTION"), 80 | } 81 | return config.DbConnection 82 | } 83 | 84 | func DbRegion() string { 85 | config := &Config{ 86 | DbRegion: viper.GetString("DB_REGION"), 87 | } 88 | return config.DbRegion 89 | } 90 | 91 | func DbHost() string { 92 | config := &Config{ 93 | DbHost: viper.GetString("DB_HOST"), 94 | } 95 | return config.DbHost 96 | } 97 | 98 | func DbPort() string { 99 | config := &Config{ 100 | DbPort: viper.GetString("DB_PORT"), 101 | } 102 | return config.DbPort 103 | } 104 | 105 | func DbDatabase() string { 106 | config := &Config{ 107 | DbDatabase: viper.GetString("DB_DATABASE"), 108 | } 109 | return config.DbDatabase 110 | } 111 | 112 | func DbUsername() string { 113 | config := &Config{ 114 | DbUsername: viper.GetString("DB_USERNAME"), 115 | } 116 | return config.DbUsername 117 | } 118 | 119 | func DbPassword() string { 120 | config := &Config{ 121 | DbPassword: viper.GetString("DB_PASSWORD"), 122 | } 123 | return config.DbPassword 124 | } 125 | 126 | func JWTAuthUsername() string { 127 | config := &Config{ 128 | JwtAuthUsername: viper.GetString("JWT_AUTH_USERNAME"), 129 | } 130 | return config.JwtAuthUsername 131 | } 132 | 133 | func JWTAuthPassword() string { 134 | config := &Config{ 135 | JwtAuthPassword: viper.GetString("JWT_AUTH_PASSWORD"), 136 | } 137 | return config.JwtAuthPassword 138 | } 139 | 140 | func JWTIssuer() string { 141 | config := &Config{ 142 | JwtIssuer: viper.GetString("JWT_AUTH_USERNAME"), 143 | } 144 | return config.JwtIssuer 145 | } 146 | 147 | func JWTSecret() string { 148 | config := &Config{ 149 | JwtSecret: viper.GetString("JWT_SECRET"), 150 | } 151 | return config.JwtSecret 152 | } 153 | -------------------------------------------------------------------------------- /src/config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestLoadConfig(t *testing.T) { 12 | os.Setenv("APP_URL", "http://localhost") 13 | os.Setenv("APP_PORT", "8080") 14 | os.Setenv("DB_CONNECTION", "sqlite") 15 | os.Setenv("DB_REGION", "ap-southeast-1") 16 | os.Setenv("DB_HOST", "localhost") 17 | os.Setenv("DB_PORT", "") 18 | os.Setenv("DB_DATABASE", "go-bookstore.db") 19 | os.Setenv("DB_USERNAME", "root") 20 | os.Setenv("DB_PASSWORD", "") 21 | os.Setenv("JWT_AUTH_USERNAME", "devopscorner") 22 | os.Setenv("JWT_AUTH_PASSWORD", "DevOpsCorner@2023") 23 | os.Setenv("JWT_SECRET", "s3cr3t") 24 | 25 | cfg, err := LoadConfig() 26 | if err != nil { 27 | log.Fatalf("error loading config: %v", err) 28 | } 29 | 30 | assert.NoError(t, err) 31 | assert.Equal(t, "http://localhost", cfg.AppUrl) 32 | assert.Equal(t, "8080", cfg.AppPort) 33 | assert.Equal(t, "ap-southeast-1", cfg.DbRegion) 34 | assert.Equal(t, "sqlite", cfg.DbConnection) 35 | assert.Equal(t, "", cfg.DbPort) 36 | assert.Equal(t, "go-bookstore.db", cfg.DbDatabase) 37 | assert.Equal(t, "root", cfg.DbUsername) 38 | assert.Equal(t, "", cfg.DbPassword) 39 | assert.Equal(t, "devopscorner", cfg.JwtAuthUsername) 40 | assert.Equal(t, "DevOpsCorner@2023", cfg.JwtAuthPassword) 41 | assert.Equal(t, "s3cr3t", cfg.JwtSecret) 42 | } 43 | -------------------------------------------------------------------------------- /src/config/const.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const ( 4 | ERR_INVALID_BOOK_ID = "Invalid book ID" 5 | ERR_INVALID_REQUEST_PAYLOAD = "Invalid request payload" 6 | ERR_INVALID_CREDENTIALS = "Invalid credentials" 7 | ERR_INVALID_AUTH_HEADER = "Invalid authorization header" 8 | ERR_INVALID_TOKEN = "Invalid token!" 9 | ERR_MISSING_AUTH_HEADER = "Missing authorization header" 10 | ERR_BOOK_NOT_FOUND = "Book not found!" 11 | ERR_UPDATE_BOOK = "Failed to update book" 12 | ERR_DELETE_BOOK = "Failed to delete book" 13 | ) 14 | -------------------------------------------------------------------------------- /src/controller/book_controller.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "strconv" 5 | 6 | "github.com/devopscorner/golang-deployment/src/model" 7 | "github.com/devopscorner/golang-deployment/src/repository" 8 | "github.com/devopscorner/golang-deployment/src/view" 9 | "github.com/gin-gonic/gin" 10 | validator "github.com/go-playground/validator/v10" 11 | ) 12 | 13 | type CreateBookInput struct { 14 | Title string `json:"title" binding:"required"` 15 | Author string `json:"author" binding:"required"` 16 | Year string `json:"year" binding:"required"` 17 | } 18 | 19 | type UpdateBookInput struct { 20 | Title string `json:"title"` 21 | Author string `json:"author"` 22 | Year string `json:"year"` 23 | } 24 | 25 | // GET /books 26 | // Find all books 27 | func GetAllBooks(ctx *gin.Context) { 28 | books := repository.GetAll() 29 | view.ViewGetAllBooks(ctx, books) 30 | } 31 | 32 | // GET /books/:id 33 | // Find a book 34 | func GetBookByID(ctx *gin.Context) { 35 | id, err := strconv.Atoi(ctx.Param("id")) 36 | if err != nil { 37 | view.ErrorInvalidId(ctx) 38 | return 39 | } 40 | 41 | book, err := repository.GetByID(strconv.Itoa(id)) 42 | if err != nil { 43 | view.ErrorInternalServer(ctx, err) 44 | return 45 | } 46 | if book == nil { 47 | view.ErrorNotFound(ctx) 48 | return 49 | } 50 | view.ViewGetBookByID(ctx, book) 51 | } 52 | 53 | // POST /books 54 | // Create new book 55 | func CreateBook(ctx *gin.Context) { 56 | // Validate input 57 | var input CreateBookInput 58 | if err := ctx.ShouldBindJSON(&input); err != nil { 59 | view.ErrorBadRequest(ctx, err) 60 | return 61 | } 62 | 63 | book := model.Book{Title: input.Title, Author: input.Author, Year: input.Year} 64 | 65 | validate := validator.New() 66 | if err := validate.Struct(book); err != nil { 67 | view.ErrorBadRequest(ctx, err) 68 | return 69 | } 70 | 71 | err := repository.Create(&book) 72 | if err != nil { 73 | view.ErrorInternalServer(ctx, err) 74 | return 75 | } 76 | view.ViewCreateBook(ctx, book) 77 | } 78 | 79 | // PUT /books/:id 80 | // Update a book 81 | func UpdateBook(ctx *gin.Context) { 82 | id, err := strconv.Atoi(ctx.Param("id")) 83 | if err != nil { 84 | view.ErrorInvalidId(ctx) 85 | return 86 | } 87 | 88 | book, err := repository.GetByID(strconv.Itoa(id)) 89 | if err != nil { 90 | view.ErrorNotFound(ctx) 91 | return 92 | } 93 | 94 | if err := ctx.ShouldBindJSON(&book); err != nil { 95 | view.ErrorInvalidRequest(ctx) 96 | return 97 | } 98 | 99 | if err := repository.Update(book); err != nil { 100 | view.ErrorUpdate(ctx) 101 | return 102 | } 103 | 104 | view.ViewUpdateBook(ctx, book) 105 | } 106 | 107 | // DELETE /books/:id 108 | // Delete a book 109 | func DeleteBook(ctx *gin.Context) { 110 | id, err := strconv.Atoi(ctx.Param("id")) 111 | if err != nil { 112 | view.ErrorInvalidId(ctx) 113 | return 114 | } 115 | 116 | if err := repository.Delete(strconv.Itoa(id)); err != nil { 117 | view.ErrorDelete(ctx) 118 | return 119 | } 120 | 121 | view.ViewDeleteBook(ctx) 122 | } 123 | -------------------------------------------------------------------------------- /src/controller/book_controller_test.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | 10 | "github.com/devopscorner/golang-deployment/src/config" 11 | "github.com/devopscorner/golang-deployment/src/model" 12 | "github.com/devopscorner/golang-deployment/src/routes" 13 | "github.com/gin-gonic/gin" 14 | "github.com/stretchr/testify/assert" 15 | ) 16 | 17 | var ( 18 | routerBook *gin.Engine 19 | ) 20 | 21 | func TestBookController_Main() { 22 | gin.SetMode(gin.TestMode) 23 | config.LoadConfig() 24 | routerBook = gin.Default() 25 | routes.SetupRoutes(routerBook) 26 | } 27 | 28 | func TestBookController_GetAllBooks(t *testing.T) { 29 | // Set up the test request 30 | req, _ := http.NewRequest(http.MethodGet, "/v1/books", nil) 31 | 32 | // Make the request to the test server 33 | w := httptest.NewRecorder() 34 | routerBook.ServeHTTP(w, req) 35 | 36 | // Check the response code and body 37 | assert.Equal(t, http.StatusOK, w.Code) 38 | 39 | var books []model.Book 40 | err := json.Unmarshal(w.Body.Bytes(), &books) 41 | assert.NoError(t, err) 42 | 43 | assert.NotEmpty(t, books) 44 | } 45 | 46 | func TestBookController_GetBookByID(t *testing.T) { 47 | // Set up the test request 48 | req, _ := http.NewRequest(http.MethodGet, "/v1/books/1", nil) 49 | 50 | // Make the request to the test server 51 | w := httptest.NewRecorder() 52 | routerBook.ServeHTTP(w, req) 53 | 54 | // Check the response code and body 55 | assert.Equal(t, http.StatusOK, w.Code) 56 | 57 | var book model.Book 58 | err := json.Unmarshal(w.Body.Bytes(), &book) 59 | assert.NoError(t, err) 60 | 61 | assert.Equal(t, uint(1), book.ID) 62 | assert.Equal(t, "Test Book", book.Title) 63 | assert.Equal(t, "Test Author", book.Author) 64 | assert.Equal(t, 2021, book.Year) 65 | } 66 | 67 | func TestBookController_CreateBook(t *testing.T) { 68 | // Set up the test request 69 | book := model.Book{Title: "Test Book", Author: "Test Author", Year: 2021} 70 | jsonBook, _ := json.Marshal(book) 71 | req, _ := http.NewRequest(http.MethodPost, "/v1/books", bytes.NewBuffer(jsonBook)) 72 | req.Header.Set("Content-Type", "application/json") 73 | 74 | // Make the request to the test server 75 | w := httptest.NewRecorder() 76 | routerBook.ServeHTTP(w, req) 77 | 78 | // Check the response code and body 79 | assert.Equal(t, http.StatusCreated, w.Code) 80 | 81 | var createdBook model.Book 82 | err := json.Unmarshal(w.Body.Bytes(), &createdBook) 83 | assert.NoError(t, err) 84 | 85 | assert.Equal(t, book.Title, createdBook.Title) 86 | assert.Equal(t, book.Author, createdBook.Author) 87 | assert.Equal(t, book.Year, createdBook.Year) 88 | } 89 | 90 | func TestBookController_UpdateBook(t *testing.T) { 91 | // Set up the test request 92 | book := model.Book{Title: "New Test Book", Author: "New Test Author", Year: 2022} 93 | jsonBook, _ := json.Marshal(book) 94 | req, _ := http.NewRequest(http.MethodPut, "/v1/books/1", bytes.NewBuffer(jsonBook)) 95 | req.Header.Set("Content-Type", "application/json") 96 | 97 | // Make the request to the test server 98 | w := httptest.NewRecorder() 99 | routerBook.ServeHTTP(w, req) 100 | 101 | // Check the response code and body 102 | assert.Equal(t, http.StatusOK, w.Code) 103 | 104 | var updatedBook model.Book 105 | err := json.Unmarshal(w.Body.Bytes(), &updatedBook) 106 | assert.NoError(t, err) 107 | 108 | assert.Equal(t, uint(1), updatedBook.ID) 109 | assert.Equal(t, "New Test Book", updatedBook.Title) 110 | assert.Equal(t, "New Test Author", updatedBook.Author) 111 | assert.Equal(t, 2022, updatedBook.Year) 112 | } 113 | 114 | func TestBookController_DeleteBook(t *testing.T) { 115 | // Set up the test request 116 | req, _ := http.NewRequest(http.MethodDelete, "/v1/books/1", nil) 117 | 118 | // Make the request to the test server 119 | w := httptest.NewRecorder() 120 | routerBook.ServeHTTP(w, req) 121 | 122 | // Check the response code and body 123 | assert.Equal(t, http.StatusOK, w.Code) 124 | 125 | var deletedBook model.Book 126 | err := json.Unmarshal(w.Body.Bytes(), &deletedBook) 127 | assert.NoError(t, err) 128 | 129 | assert.Equal(t, uint(1), deletedBook.ID) 130 | assert.Equal(t, "New Test Book", deletedBook.Title) 131 | assert.Equal(t, "New Test Author", deletedBook.Author) 132 | assert.Equal(t, 2022, deletedBook.Year) 133 | } 134 | -------------------------------------------------------------------------------- /src/controller/login_controller.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/devopscorner/golang-deployment/src/middleware" 5 | "github.com/devopscorner/golang-deployment/src/view" 6 | "github.com/gin-gonic/gin" 7 | validator "github.com/go-playground/validator/v10" 8 | "github.com/spf13/viper" 9 | ) 10 | 11 | type LoginRequest struct { 12 | Username string `json:"username" binding:"required"` 13 | Password string `json:"password" binding:"required"` 14 | } 15 | 16 | var loginRequest LoginRequest 17 | 18 | func LoginUser(ctx *gin.Context) { 19 | if err := ctx.BindJSON(&loginRequest); err != nil { 20 | view.ErrorBadRequest(ctx, err) 21 | return 22 | } 23 | 24 | validate := validator.New() 25 | if err := validate.Struct(loginRequest); err != nil { 26 | view.ErrorBadRequest(ctx, err) 27 | return 28 | } 29 | 30 | validCred := middleware.ValidateCredentials(loginRequest.Username, loginRequest.Password) 31 | if validCred != true { 32 | view.ErrorInvalidCredentials(ctx) 33 | return 34 | } 35 | 36 | token, err := middleware.GenerateToken(viper.GetString("JWT_SECRET"), viper.GetString("JWT_AUTH_USERNAME")) 37 | if err != nil { 38 | view.ErrorInternalServer(ctx, err) 39 | return 40 | } 41 | 42 | view.LoginToken(ctx, token) 43 | } 44 | -------------------------------------------------------------------------------- /src/controller/login_controller_test.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | 10 | "github.com/devopscorner/golang-deployment/src/config" 11 | "github.com/devopscorner/golang-deployment/src/controller" 12 | "github.com/devopscorner/golang-deployment/src/routes" 13 | "github.com/gin-gonic/gin" 14 | "github.com/stretchr/testify/assert" 15 | ) 16 | 17 | var ( 18 | routerLogin *gin.Engine 19 | ) 20 | 21 | func TestLoginController_Main() { 22 | gin.SetMode(gin.TestMode) 23 | config.LoadConfig() 24 | routerLogin = gin.Default() 25 | routes.SetupRoutes(routerBook) 26 | } 27 | 28 | func TestLoginController_CreateToken(t *testing.T) { 29 | // Set up the test request 30 | loginRequest := controller.LoginRequest{Username: "admin", Password: "password"} 31 | jsonRequest, _ := json.Marshal(loginRequest) 32 | req, _ := http.NewRequest(http.MethodPost, "/login", bytes.NewBuffer(jsonRequest)) 33 | req.Header.Set("Content-Type", "application/json") 34 | 35 | // Make the request to the test server 36 | w := httptest.NewRecorder() 37 | routerLogin.ServeHTTP(w, req) 38 | 39 | // Check the response code and body 40 | assert.Equal(t, http.StatusOK, w.Code) 41 | 42 | var responseBody map[string]string 43 | err := json.Unmarshal(w.Body.Bytes(), &responseBody) 44 | assert.NoError(t, err) 45 | 46 | assert.NotEmpty(t, responseBody["token"]) 47 | } 48 | -------------------------------------------------------------------------------- /src/driver/db.go: -------------------------------------------------------------------------------- 1 | package driver 2 | 3 | import ( 4 | "github.com/devopscorner/golang-deployment/src/config" 5 | "github.com/guregu/dynamo" 6 | "gorm.io/gorm" 7 | ) 8 | 9 | var DB *gorm.DB 10 | var DYN *dynamo.DB 11 | 12 | func ConnectDatabase() { 13 | if config.DbConnection() == "mysql" { 14 | ConnectMySQL() 15 | DB = DB_MySQL 16 | } else if config.DbConnection() == "postgres" { 17 | ConnectPSQL() 18 | DB = DB_PSQL 19 | } else if config.DbConnection() == "dynamo" { 20 | ConnectDynamo() 21 | DYN = DB_Dynamo 22 | } else { 23 | ConnectSQLite() 24 | DB = DB_SQLite 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/driver/dynamo.go: -------------------------------------------------------------------------------- 1 | package driver 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/aws/aws-sdk-go/aws" 7 | "github.com/aws/aws-sdk-go/aws/session" 8 | "github.com/devopscorner/golang-deployment/src/config" 9 | "github.com/guregu/dynamo" 10 | ) 11 | 12 | var DB_Dynamo *dynamo.DB 13 | 14 | func ConnectDynamo() { 15 | _, err := config.LoadConfig() 16 | if err != nil { 17 | log.Fatalf("error loading config: %v", err) 18 | } 19 | 20 | sess := session.Must(session.NewSession()) 21 | database := dynamo.New(sess, &aws.Config{ 22 | Region: aws.String(config.DbDatabase()), 23 | }) 24 | 25 | database.Table(config.DbDatabase()) 26 | 27 | DB_Dynamo = database 28 | } 29 | -------------------------------------------------------------------------------- /src/driver/mysql.go: -------------------------------------------------------------------------------- 1 | package driver 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/devopscorner/golang-deployment/src/config" 7 | "github.com/devopscorner/golang-deployment/src/model" 8 | "gorm.io/driver/mysql" 9 | "gorm.io/gorm" 10 | ) 11 | 12 | var DB_MySQL *gorm.DB 13 | 14 | func ConnectMySQL() { 15 | _, err := config.LoadConfig() 16 | if err != nil { 17 | log.Fatalf("error loading config: %v", err) 18 | } 19 | 20 | // References: 21 | // https://gorm.io/docs/connecting_to_the_database.html 22 | 23 | // Simple Connection 24 | dsn := "user:pass@tcp(" + config.DbHost() + ":" + config.DbPort() + ")/" + config.DbDatabase() + "?charset=utf8mb4&parseTime=True&loc=Local" 25 | database, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) 26 | 27 | // Advanced Connection 28 | // dsn := "gorm:gorm@tcp(" + config.DbHost() + ":" + config.DbPort() + ")/" + config.DbDatabase() + "?charset=utf8&parseTime=True&loc=Local" 29 | // database, err := gorm.Open(mysql.New(mysql.Config{ 30 | // DSN: dsn, // data source name 31 | // DefaultStringSize: 256, // default size for string fields 32 | // DisableDatetimePrecision: true, // disable datetime precision, which not supported before MySQL 5.6 33 | // DontSupportRenameIndex: true, // drop & create when rename index, rename index not supported before MySQL 5.7, MariaDB 34 | // DontSupportRenameColumn: true, // `change` when rename column, rename column not supported before MySQL 8, MariaDB 35 | // SkipInitializeWithVersion: false, // auto configure based on currently MySQL version 36 | // }), &gorm.Config{}) 37 | 38 | if err != nil { 39 | panic("Failed to connect to SQLite!") 40 | } 41 | 42 | // Migrate the schema 43 | database.AutoMigrate(&model.Book{}) 44 | DB_MySQL = database 45 | } 46 | -------------------------------------------------------------------------------- /src/driver/psql.go: -------------------------------------------------------------------------------- 1 | package driver 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/devopscorner/golang-deployment/src/config" 7 | "github.com/devopscorner/golang-deployment/src/model" 8 | "gorm.io/driver/postgres" 9 | "gorm.io/gorm" 10 | ) 11 | 12 | var DB_PSQL *gorm.DB 13 | 14 | func ConnectPSQL() { 15 | _, err := config.LoadConfig() 16 | if err != nil { 17 | log.Fatalf("error loading config: %v", err) 18 | } 19 | 20 | // References: 21 | // https://gorm.io/docs/connecting_to_the_database.html 22 | DSN_HOST := "host=" + config.DbHost() 23 | DSN_PORT := "port=" + config.DbPort() 24 | DSN_USER := "user=" + config.DbUsername() 25 | DSN_PASSWORD := "user=" + config.DbPassword() 26 | DSN_DBNAME := "dbname=" + config.DbDatabase() 27 | 28 | dsn := DSN_HOST + " " + DSN_PORT + " " + DSN_USER + " " + DSN_PASSWORD + " " + DSN_DBNAME + "sslmode=disable TimeZone=Asia/Jakarta" 29 | 30 | database, err := gorm.Open(postgres.New(postgres.Config{ 31 | DSN: dsn, 32 | PreferSimpleProtocol: true, 33 | }), &gorm.Config{}) 34 | 35 | if err != nil { 36 | panic("Failed to connect to Postgres!") 37 | } 38 | 39 | // Migrate the schema 40 | database.AutoMigrate(&model.Book{}) 41 | DB_PSQL = database 42 | } 43 | -------------------------------------------------------------------------------- /src/driver/sqlite.go: -------------------------------------------------------------------------------- 1 | package driver 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/devopscorner/golang-deployment/src/config" 7 | "github.com/devopscorner/golang-deployment/src/model" 8 | "gorm.io/driver/sqlite" 9 | "gorm.io/gorm" 10 | ) 11 | 12 | var DB_SQLite *gorm.DB 13 | 14 | func ConnectSQLite() { 15 | _, err := config.LoadConfig() 16 | if err != nil { 17 | log.Fatalf("error loading config: %v", err) 18 | } 19 | 20 | database, err := gorm.Open(sqlite.Open(config.DbDatabase()), &gorm.Config{}) 21 | 22 | if err != nil { 23 | panic("Failed to connect to SQLite!") 24 | } 25 | 26 | // Migrate the schema 27 | database.AutoMigrate(&model.Book{}) 28 | DB_SQLite = database 29 | } 30 | -------------------------------------------------------------------------------- /src/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/devopscorner/golang-deployment/src 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go v1.44.221 7 | github.com/gin-gonic/gin v1.9.1 8 | github.com/go-playground/validator/v10 v10.14.0 9 | github.com/golang-jwt/jwt v3.2.2+incompatible 10 | github.com/guregu/dynamo v1.18.2 11 | github.com/spf13/viper v1.15.0 12 | github.com/stretchr/testify v1.8.3 13 | gorm.io/driver/mysql v1.4.7 14 | gorm.io/driver/postgres v1.5.0 15 | gorm.io/driver/sqlite v1.4.4 16 | gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11 17 | ) 18 | 19 | require ( 20 | github.com/bytedance/sonic v1.9.1 // indirect 21 | github.com/cenkalti/backoff/v4 v4.1.2 // indirect 22 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 23 | github.com/davecgh/go-spew v1.1.1 // indirect 24 | github.com/fsnotify/fsnotify v1.6.0 // indirect 25 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 26 | github.com/gin-contrib/sse v0.1.0 // indirect 27 | github.com/go-playground/locales v0.14.1 // indirect 28 | github.com/go-playground/universal-translator v0.18.1 // indirect 29 | github.com/go-sql-driver/mysql v1.7.0 // indirect 30 | github.com/goccy/go-json v0.10.2 // indirect 31 | github.com/hashicorp/hcl v1.0.0 // indirect 32 | github.com/jackc/pgpassfile v1.0.0 // indirect 33 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect 34 | github.com/jackc/pgx/v5 v5.5.4 // indirect 35 | github.com/jackc/puddle/v2 v2.2.1 // indirect 36 | github.com/jinzhu/inflection v1.0.0 // indirect 37 | github.com/jinzhu/now v1.1.5 // indirect 38 | github.com/jmespath/go-jmespath v0.4.0 // indirect 39 | github.com/json-iterator/go v1.1.12 // indirect 40 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 41 | github.com/leodido/go-urn v1.2.4 // indirect 42 | github.com/magiconair/properties v1.8.7 // indirect 43 | github.com/mattn/go-isatty v0.0.19 // indirect 44 | github.com/mattn/go-sqlite3 v1.14.15 // indirect 45 | github.com/mitchellh/mapstructure v1.5.0 // indirect 46 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 47 | github.com/modern-go/reflect2 v1.0.2 // indirect 48 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 49 | github.com/pmezard/go-difflib v1.0.0 // indirect 50 | github.com/spf13/afero v1.9.3 // indirect 51 | github.com/spf13/cast v1.5.0 // indirect 52 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 53 | github.com/spf13/pflag v1.0.5 // indirect 54 | github.com/subosito/gotenv v1.4.2 // indirect 55 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 56 | github.com/ugorji/go/codec v1.2.11 // indirect 57 | golang.org/x/arch v0.3.0 // indirect 58 | golang.org/x/crypto v0.35.0 // indirect 59 | golang.org/x/net v0.36.0 // indirect 60 | golang.org/x/sync v0.11.0 // indirect 61 | golang.org/x/sys v0.30.0 // indirect 62 | golang.org/x/text v0.22.0 // indirect 63 | google.golang.org/protobuf v1.33.0 // indirect 64 | gopkg.in/ini.v1 v1.67.0 // indirect 65 | gopkg.in/yaml.v3 v3.0.1 // indirect 66 | ) 67 | -------------------------------------------------------------------------------- /src/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/devopscorner/golang-deployment/src/routes" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | func main() { 9 | router := gin.Default() 10 | 11 | // Activate: Production Mode 12 | // gin.SetMode(gin.ReleaseMode) 13 | 14 | // Set Router 15 | routes.SetupRoutes(router) 16 | } 17 | -------------------------------------------------------------------------------- /src/main_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | "github.com/devopscorner/golang-deployment/src/config" 10 | "github.com/devopscorner/golang-deployment/src/routes" 11 | "github.com/gin-gonic/gin" 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | var ( 16 | router *gin.Engine 17 | ) 18 | 19 | func TestMain(m *testing.M) { 20 | gin.SetMode(gin.TestMode) 21 | 22 | _, err := config.LoadConfig() 23 | if err != nil { 24 | log.Fatalf("error loading config: %v", err) 25 | } 26 | 27 | router = gin.Default() 28 | 29 | routes.SetupRoutes(router) 30 | 31 | w := httptest.NewRecorder() 32 | req, _ := http.NewRequest(http.MethodGet, "/v1/books", nil) 33 | router.ServeHTTP(w, req) 34 | 35 | assert.Equal(t, http.StatusOK, w.Code) 36 | } 37 | 38 | func TestBookstoreAPI(t *testing.T) { 39 | req, err := http.NewRequest(http.MethodGet, "/v1/books", nil) 40 | assert.NoError(t, err) 41 | 42 | w := httptest.NewRecorder() 43 | router.ServeHTTP(w, req) 44 | 45 | assert.Equal(t, http.StatusUnauthorized, w.Code) 46 | 47 | req.Header.Set("Authorization", "Basic dGVzdHVzZXI6dGVzdHBhc3M=") 48 | w = httptest.NewRecorder() 49 | router.ServeHTTP(w, req) 50 | 51 | assert.Equal(t, http.StatusOK, w.Code) 52 | } 53 | -------------------------------------------------------------------------------- /src/middleware/auth_middleware.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | "time" 9 | 10 | "github.com/devopscorner/golang-deployment/src/config" 11 | "github.com/gin-gonic/gin" 12 | "github.com/golang-jwt/jwt" 13 | "github.com/spf13/viper" 14 | ) 15 | 16 | func AuthMiddleware() gin.HandlerFunc { 17 | return func(ctx *gin.Context) { 18 | authHeader := ctx.GetHeader("Authorization") 19 | if authHeader == "" { 20 | ctx.JSON(http.StatusUnauthorized, gin.H{"error": config.ERR_MISSING_AUTH_HEADER}) 21 | ctx.Abort() 22 | return 23 | } 24 | 25 | authHeaderParts := strings.Split(authHeader, " ") 26 | if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "bearer" { 27 | ctx.JSON(http.StatusUnauthorized, gin.H{"error": config.ERR_MISSING_AUTH_HEADER}) 28 | ctx.Abort() 29 | return 30 | } 31 | 32 | tokenString := authHeaderParts[1] 33 | token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { 34 | if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { 35 | return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) 36 | } 37 | return []byte(config.JWTSecret()), nil 38 | }) 39 | 40 | if err != nil { 41 | ctx.JSON(http.StatusUnauthorized, gin.H{"error": config.ERR_INVALID_TOKEN}) 42 | ctx.Abort() 43 | return 44 | } 45 | 46 | if !token.Valid { 47 | ctx.JSON(http.StatusUnauthorized, gin.H{"error": config.ERR_INVALID_TOKEN}) 48 | ctx.Abort() 49 | return 50 | } 51 | 52 | ctx.Set("Issuer", token.Claims.(jwt.MapClaims)[config.JWTIssuer()]) 53 | ctx.Next() 54 | } 55 | } 56 | 57 | func GenerateToken(secret string, issuer string) (string, error) { 58 | // Set the expiration time to 1 hour from now 59 | expirationTime := time.Now().Add(time.Hour * 1).Unix() 60 | 61 | // Create the JWT claims 62 | claims := jwt.StandardClaims{ 63 | Issuer: config.JWTIssuer(), 64 | ExpiresAt: expirationTime, 65 | } 66 | 67 | // Create the JWT token with the claims and secret 68 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 69 | tokenString, err := token.SignedString([]byte(config.JWTSecret())) 70 | if err != nil { 71 | return "", errors.New("Failed to generate token") 72 | } 73 | 74 | return tokenString, nil 75 | } 76 | 77 | func ValidateCredentials(username string, password string) bool { 78 | return username == viper.GetString("JWT_AUTH_USERNAME") && password == viper.GetString("JWT_AUTH_PASSWORD") 79 | } 80 | -------------------------------------------------------------------------------- /src/middleware/auth_middleware_test.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | 8 | "github.com/devopscorner/golang-deployment/src/config" 9 | "github.com/devopscorner/golang-deployment/src/middleware" 10 | "github.com/gin-gonic/gin" 11 | jwt "github.com/golang-jwt/jwt" 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestAuthMiddleware(t *testing.T) { 16 | gin.SetMode(gin.TestMode) 17 | 18 | w := httptest.NewRecorder() 19 | c, r := gin.CreateTestContext(w) 20 | 21 | r.GET("/v1/books", middleware.AuthMiddleware(), func(c *gin.Context) { 22 | c.JSON(http.StatusOK, gin.H{}) 23 | }) 24 | 25 | c.Request, _ = http.NewRequest(http.MethodGet, "/v1/books", nil) 26 | c.Request.Header.Set("Authorization", "Bearer "+generateToken_Test()) 27 | 28 | r.ServeHTTP(w, c.Request) 29 | 30 | assert.Equal(t, http.StatusOK, w.Code) 31 | } 32 | 33 | func generateToken_Test() string { 34 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ 35 | "user_id": "123", 36 | }) 37 | tokenString, _ := token.SignedString([]byte(config.JWTSecret())) 38 | return tokenString 39 | } 40 | -------------------------------------------------------------------------------- /src/migrate_book.go.example: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/devopscorner/golang-deployment/src/config" 8 | "github.com/devopscorner/golang-deployment/src/driver" 9 | "github.com/devopscorner/golang-deployment/src/model" 10 | "gorm.io/gorm" 11 | ) 12 | 13 | func main() { 14 | _, err := config.LoadConfig() 15 | if err != nil { 16 | log.Fatalf("error loading config: %v", err) 17 | } 18 | 19 | // Connect to database 20 | driver.ConnectDatabase() 21 | 22 | MigrateBook(driver.DB) 23 | fmt.Println("Sample books created successfully!") 24 | } 25 | 26 | func MigrateBook(db *gorm.DB) error { 27 | if err := db.AutoMigrate(&model.Book{}); err != nil { 28 | return err 29 | } 30 | 31 | books := []model.Book{ 32 | {Title: "The Great Gatsby", Author: "F. Scott Fitzgerald", Year: "1925"}, 33 | {Title: "To Kill a Mockingbird", Author: "Harper Lee", Year: "1960"}, 34 | {Title: "1984", Author: "George Orwell", Year: "1949"}, 35 | {Title: "Animal Farm", Author: "George Orwell", Year: "1945"}, 36 | {Title: "The Catcher in the Rye", Author: "J.D. Salinger", Year: "1951"}, 37 | {Title: "One Hundred Years of Solitude", Author: "Gabriel Garcia Marquez", Year: "1967"}, 38 | {Title: "Moby-Dick", Author: "Herman Melville", Year: "1851"}, 39 | {Title: "Pride and Prejudice", Author: "Jane Austen", Year: "1813"}, 40 | } 41 | 42 | for _, book := range books { 43 | if err := db.Create(&book).Error; err != nil { 44 | fmt.Println("Failed to insert data!") 45 | return err 46 | } 47 | } 48 | 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /src/migrate_book_dynamo.go.example: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | 8 | "github.com/devopscorner/golang-deployment-adot/src/config" 9 | "github.com/devopscorner/golang-deployment-adot/src/driver" 10 | "github.com/devopscorner/golang-deployment-adot/src/model" 11 | "github.com/guregu/dynamo" 12 | ) 13 | 14 | func main() { 15 | _, err := config.LoadConfig() 16 | if err != nil { 17 | log.Fatalf("error loading config: %v", err) 18 | } 19 | 20 | // Connect to database 21 | driver.ConnectDatabase() 22 | 23 | MigrateBook(driver.DYN) 24 | fmt.Println("Sample books created successfully!") 25 | } 26 | 27 | func MigrateBook(db *dynamo.DB) error { 28 | 29 | books := []model.Book{ 30 | {Title: "The Great Gatsby", Author: "F. Scott Fitzgerald", Year: "1925"}, 31 | {Title: "To Kill a Mockingbird", Author: "Harper Lee", Year: "1960"}, 32 | {Title: "1984", Author: "George Orwell", Year: "1949"}, 33 | {Title: "Animal Farm", Author: "George Orwell", Year: "1945"}, 34 | {Title: "The Catcher in the Rye", Author: "J.D. Salinger", Year: "1951"}, 35 | {Title: "One Hundred Years of Solitude", Author: "Gabriel Garcia Marquez", Year: "1967"}, 36 | {Title: "Moby-Dick", Author: "Herman Melville", Year: "1851"}, 37 | {Title: "Pride and Prejudice", Author: "Jane Austen", Year: "1813"}, 38 | } 39 | 40 | for _, book := range books { 41 | book.ID = fmt.Sprintf("%d", time.Now().UnixNano()) 42 | if err := driver.DYN.Table(config.DbDatabase()).Put(book).Run(); err != nil { 43 | fmt.Println("Failed to insert data!") 44 | return err 45 | } 46 | } 47 | 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /src/model/book.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "gorm.io/gorm" 4 | 5 | // -------------------------------------- 6 | // SQLite, MySQL, Postgres 7 | // -------------------------------------- 8 | // ORM: gorm.io/gorm 9 | // gorm.io/driver/sqlite 10 | 11 | type Book struct { 12 | gorm.Model 13 | Title string `json:"title" validate:"required"` 14 | Author string `json:"author" validate:"required"` 15 | Year string `json:"year" validate:"required"` 16 | } 17 | 18 | // import "github.com/guregu/dynamo" 19 | 20 | // -------------------------------------- 21 | // DynamoDB 22 | // -------------------------------------- 23 | // ORM: github.com/guregu/dynamo 24 | 25 | // type Book struct { 26 | // ID string `dynamo:"id"` 27 | // Title string `dynamo:"title" validate:"required"` 28 | // Author string `dynamo:"author" validate:"required"` 29 | // Year string `dynamo:"year" validate:"required"` 30 | // } 31 | -------------------------------------------------------------------------------- /src/repository/book_repository.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "github.com/devopscorner/golang-deployment/src/config" 5 | "github.com/devopscorner/golang-deployment/src/driver" 6 | "github.com/devopscorner/golang-deployment/src/model" 7 | "github.com/guregu/dynamo" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | func init() { 12 | // Connect to database 13 | driver.ConnectDatabase() 14 | } 15 | 16 | func GetAll() []model.Book { 17 | var books []model.Book 18 | 19 | if config.DbConnection() == "dynamo" { 20 | driver.DYN.Table(config.DbDatabase()).Scan().All(&books) 21 | } else { 22 | driver.DB.Find(&books) 23 | } 24 | 25 | return books 26 | } 27 | 28 | func GetByID(id string) (*model.Book, error) { 29 | var book model.Book 30 | 31 | if config.DbConnection() == "dynamo" { 32 | err := driver.DYN.Table(config.DbDatabase()).Scan().Filter("'id' > ?", id).All(&book) 33 | if err != nil { 34 | if err == dynamo.ErrNotFound { 35 | return nil, nil 36 | } 37 | return nil, err 38 | } 39 | } else { 40 | err := driver.DB.First(&book, id).Error 41 | if err != nil { 42 | if err == gorm.ErrRecordNotFound { 43 | return nil, nil 44 | } 45 | return nil, err 46 | } 47 | } 48 | 49 | return &book, nil 50 | } 51 | 52 | func Create(book *model.Book) error { 53 | if config.DbConnection() == "dynamo" { 54 | // book.ID = fmt.Sprintf("%d", time.Now().UnixNano()) 55 | return driver.DYN.Table(config.DbDatabase()).Put(book).Run() 56 | } else { 57 | return driver.DB.Create(&book).Error 58 | } 59 | } 60 | 61 | func Update(book *model.Book) error { 62 | if config.DbConnection() == "dynamo" { 63 | return driver.DYN.Table(config.DbDatabase()).Put(book).Run() 64 | } else { 65 | return driver.DB.Save(&book).Error 66 | } 67 | } 68 | 69 | func Delete(id string) error { 70 | if config.DbConnection() == "dynamo" { 71 | return driver.DYN.Table(config.DbDatabase()).Delete("id", id).Run() 72 | } else { 73 | return driver.DB.Delete(&model.Book{}, id).Error 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/routes/book_routes.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/devopscorner/golang-deployment/src/config" 8 | "github.com/devopscorner/golang-deployment/src/controller" 9 | "github.com/devopscorner/golang-deployment/src/driver" 10 | "github.com/devopscorner/golang-deployment/src/middleware" 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | func SetupRoutes(router *gin.Engine) { 15 | // Load Config 16 | config.LoadConfig() 17 | 18 | // Connect to database 19 | driver.ConnectDatabase() 20 | 21 | // Routes Healthcheck 22 | router.GET("/health", func(c *gin.Context) { 23 | c.String(http.StatusOK, "OK") 24 | }) 25 | // Routes Welcome 26 | router.GET("/", func(c *gin.Context) { 27 | c.String(http.StatusOK, "Welcome to Simple API Bookstore!") 28 | }) 29 | 30 | // Login route to create basic auth JWT token 31 | router.POST("/v1/login", controller.LoginUser) 32 | 33 | api := router.Group("/v1", middleware.AuthMiddleware()) 34 | { 35 | // Book routes 36 | api.GET("/books", controller.GetAllBooks) 37 | api.GET("/books/:id", controller.GetBookByID) 38 | api.POST("/books", controller.CreateBook) 39 | api.PUT("/books/:id", controller.UpdateBook) 40 | api.DELETE("/books/:id", controller.DeleteBook) 41 | } 42 | 43 | // Run the server 44 | port := fmt.Sprintf(":%v", config.AppPort()) 45 | router.Run(port) 46 | } 47 | -------------------------------------------------------------------------------- /src/view/book_view.go: -------------------------------------------------------------------------------- 1 | package view 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/devopscorner/golang-deployment/src/model" 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | // ----- View Response ----- 11 | 12 | // GET /books 13 | // Find all books 14 | func ViewGetAllBooks(ctx *gin.Context, viewBooks []model.Book) { 15 | ctx.JSON(http.StatusOK, gin.H{"data": viewBooks}) 16 | } 17 | 18 | // GET /books/:id 19 | // Find a book 20 | func ViewGetBookByID(ctx *gin.Context, viewBook *model.Book) { 21 | ctx.JSON(http.StatusOK, gin.H{"data": viewBook}) 22 | } 23 | 24 | // POST /books 25 | // Create new book 26 | func ViewCreateBook(ctx *gin.Context, viewBook model.Book) { 27 | ctx.JSON(http.StatusCreated, gin.H{"data": viewBook}) 28 | } 29 | 30 | // PUT /books/:id 31 | // Update a book 32 | func ViewUpdateBook(ctx *gin.Context, viewBook *model.Book) { 33 | ctx.JSON(http.StatusOK, gin.H{"data": viewBook}) 34 | } 35 | 36 | // DELETE /books/:id 37 | // Delete a book 38 | func ViewDeleteBook(ctx *gin.Context) { 39 | ctx.JSON(http.StatusOK, gin.H{"data": true}) 40 | } 41 | -------------------------------------------------------------------------------- /src/view/error_view.go: -------------------------------------------------------------------------------- 1 | package view 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/devopscorner/golang-deployment/src/config" 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | // ----- Error Response ----- 11 | 12 | func ErrorBadRequest(ctx *gin.Context, err error) { 13 | ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) 14 | } 15 | 16 | func ErrorInternalServer(ctx *gin.Context, err error) { 17 | ctx.JSON(http.StatusInternalServerError, gin.H{"error": err}) 18 | } 19 | 20 | func ErrorInvalidId(ctx *gin.Context) { 21 | ctx.JSON(http.StatusBadRequest, gin.H{"error": config.ERR_INVALID_BOOK_ID}) 22 | } 23 | 24 | func ErrorInvalidCredentials(ctx *gin.Context) { 25 | ctx.JSON(http.StatusUnauthorized, gin.H{"error": config.ERR_INVALID_CREDENTIALS}) 26 | } 27 | 28 | func ErrorNotFound(ctx *gin.Context) { 29 | ctx.JSON(http.StatusNotFound, gin.H{"error": config.ERR_BOOK_NOT_FOUND}) 30 | } 31 | 32 | func ErrorInvalidRequest(ctx *gin.Context) { 33 | ctx.JSON(http.StatusBadRequest, gin.H{"error": config.ERR_INVALID_REQUEST_PAYLOAD}) 34 | } 35 | 36 | func ErrorUpdate(ctx *gin.Context) { 37 | ctx.JSON(http.StatusInternalServerError, gin.H{"error": config.ERR_UPDATE_BOOK}) 38 | } 39 | 40 | func ErrorDelete(ctx *gin.Context) { 41 | ctx.JSON(http.StatusInternalServerError, gin.H{"error": config.ERR_DELETE_BOOK}) 42 | } 43 | -------------------------------------------------------------------------------- /src/view/login_view.go: -------------------------------------------------------------------------------- 1 | package view 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | // ----- View Response ----- 10 | 11 | func LoginToken(ctx *gin.Context, token string) { 12 | ctx.JSON(http.StatusOK, gin.H{"token": token}) 13 | } 14 | -------------------------------------------------------------------------------- /start-build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # ----------------------------------------------------------------------------- 3 | # Docker Build Container 4 | # ----------------------------------------------------------------------------- 5 | # Author : Dwi Fahni Denni 6 | # License : Apache v2 7 | # ----------------------------------------------------------------------------- 8 | set -e 9 | 10 | export CI_PROJECT_PATH="devopscorner" 11 | export CI_PROJECT_NAME="bookstore" 12 | 13 | export IMAGE="$CI_PROJECT_PATH/$CI_PROJECT_NAME" 14 | 15 | TAG="alpine-3.15" 16 | echo " Build Image => $IMAGE:$TAG" 17 | docker build --no-cache -f Dockerfile.alpine-3.15 -t $IMAGE:$TAG . 18 | docker tag $IMAGE:$TAG $IMAGE:go1.19-alpine3.15 19 | docker tag $IMAGE:$TAG $IMAGE:go1.19.3-alpine3.15 20 | echo "" 21 | 22 | TAG="alpine-3.16" 23 | echo " Build Image => $IMAGE:$TAG" 24 | docker build --no-cache -f Dockerfile.alpine-3.16 -t $IMAGE:$TAG . 25 | docker tag $IMAGE:$TAG $IMAGE:go1.19-alpine3.16 26 | docker tag $IMAGE:$TAG $IMAGE:go1.19.5-alpine3.16 27 | echo "" 28 | 29 | TAG="alpine-3.17" 30 | echo " Build Image => $IMAGE:$TAG" 31 | docker build --no-cache -f Dockerfile.alpine-3.17 -t $IMAGE:$TAG . 32 | docker tag $IMAGE:$TAG $IMAGE:go1.19-alpine3.17 33 | docker tag $IMAGE:$TAG $IMAGE:go1.19.5-alpine3.17 34 | docker tag $IMAGE:$TAG $IMAGE:alpine 35 | docker tag $IMAGE:$TAG $IMAGE:alpine-latest 36 | docker tag $IMAGE:$TAG $IMAGE:latest 37 | echo "" 38 | 39 | echo "Cleanup Unknown Tags" 40 | echo "docker images -a | grep none | awk '{ print $3; }' | xargs docker rmi" 41 | docker images -a | grep none | awk '{ print $3; }' | xargs docker rmi 42 | echo "" 43 | 44 | echo "-- ALL DONE --" 45 | --------------------------------------------------------------------------------