Auto-deploy smoothie

Ayman Bagabas created

- Use GoReleaser
- Add Dockerfile based on Alpine:latest
- Publish docker images to our private ghcr.io registry
- Run build and test using GH Actions (ci.yml)
- Deploy AWS ECS Docker instance to our AWS infrastructure using GH
  Actions (cd.yml)

  Note: repository secrets are used to make the auto-deploy work. All
  the secrets used are in our Gopass repository. `GITHUB_TOKEN` is a
  reserved GH Actions secret with basic permissions like pushing to
  organization registries.

Change summary

.github/workflows/cd.yml | 156 ++++++++++++++++++++++++++++++++++++++++++
.github/workflows/ci.yml |  33 ++++++++
.gitignore               |   4 +
.goreleaser.yml          |  37 +++++++++
Dockerfile               |  22 +++++
main.tf                  |  39 ++++++++++
6 files changed, 291 insertions(+)

Detailed changes

.github/workflows/cd.yml 🔗

@@ -0,0 +1,156 @@
+name: CD
+
+on:
+  push:
+    branches:
+      - main
+  pull_request:
+    
+
+jobs:
+  cd:
+    strategy:
+      matrix:
+        go-version: [~1.16]
+    runs-on: ubuntu-latest
+    env:
+      GO111MODULE: "on"
+      CONTAINER_REPO: "ghcr.io/${{ github.repository }}"
+      ENVIRONMENT: development
+      AWS_DEFAULT_REGION: us-east-1
+      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
+      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+
+    steps:
+    - name: Install Go
+      uses: actions/setup-go@v1
+      with:
+        go-version: ${{ matrix.go-version }}
+
+    - name: Checkout code
+      uses: actions/checkout@v2
+      with:
+        fetch-depth: 0
+
+    # Remove this later
+    - name: Clone internal repositories
+      run: |
+        git clone -b release https://${{ secrets.ACCESS_TOKEN }}@github.com/charmbracelet/charm-internal ../charm
+        git clone -b master https://${{ secrets.ACCESS_TOKEN }}@github.com/charmbracelet/bubbletea-internal ../bubbletea
+
+    - name: Login to GitHub Container Registry
+      uses: docker/login-action@v1
+      if: github.event_name == 'push'
+      with:
+        registry: ghcr.io
+        username: ${{ github.repository_owner }}
+        password: ${{ secrets.GITHUB_TOKEN }}
+
+    - name: Build Docker images using GoReleaser
+      uses: goreleaser/goreleaser-action@master
+      if: github.event_name == 'push'
+      with:
+        version: latest
+        # https://github.com/goreleaser/goreleaser/discussions/1534
+        args: -f .goreleaser.yml --snapshot
+
+    - name: Push Docker images
+      if: github.event_name == 'push'
+      run: |
+        docker push $CONTAINER_REPO:snapshot
+        docker push $CONTAINER_REPO:$GITHUB_SHA-snapshot
+
+    - name: Setup Terraform
+      uses: hashicorp/setup-terraform@v1
+      with:
+        # terraform_version: 0.13.0
+        cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}
+
+    - name: Terraform Variables
+      id: tfvars
+      run: |
+        TF_VARS=$(cat <<EOF
+          -var "environment=$ENVIRONMENT" \
+          -var "aws_region=$AWS_DEFAULT_REGION" \
+          -var "app_image=$CONTAINER_REPO:$GITHUB_SHA-snapshot"
+        EOF
+        )
+        echo "::set-output name=vars::$TF_VARS"
+
+    - name: Terraform Format
+      id: fmt
+      run: terraform fmt -check
+
+    - name: Terraform Init
+      id: init
+      run: terraform init
+
+    - name: Terraform Validate
+      id: validate
+      run: terraform validate -no-color
+
+    - name: Terraform Plan
+      id: plan
+      if: github.event_name == 'pull_request'
+      run: terraform plan -no-color ${{ steps.tfvars.outputs.vars }}
+      continue-on-error: true
+
+    - name: Find Comment
+      if: github.event_name == 'pull_request'
+      uses: peter-evans/find-comment@v1.2.0
+      id: fc
+      with:
+        issue-number: ${{ github.event.pull_request.number }}
+        comment-author: github-actions[bot]
+        body-includes: Terraform Summary
+
+    - name: Update Pull Request
+      uses: actions/github-script@0.9.0
+      if: github.event_name == 'pull_request'
+      env:
+        PLAN: "${{ steps.plan.outputs.stdout }}"
+        COMMENT_ID: "${{ steps.fc.outputs.comment-id }}"
+      with:
+        github-token: ${{ secrets.GITHUB_TOKEN }}
+        script: |
+          const output = `## Terraform Summary
+          - Terraform Format and Style 🖌 \`${{ steps.fmt.outcome }}\`
+          - Terraform Initialization ⚙️ \`${{ steps.init.outcome }}\`
+          - Terraform Plan 📖 \`${{ steps.plan.outcome }}\`
+          - Terraform Validation 🤖 \`${{ steps.validate.outcome }}\`
+
+          <details><summary>Show Plan</summary>
+
+          \`\`\`\n
+          ${process.env.PLAN}
+          \`\`\`
+
+          </details>
+
+          *Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`;
+
+          if (process.env.COMMENT_ID) {
+            github.issues.updateComment({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              comment_id: process.env.COMMENT_ID,
+              body: output
+            })
+          } else {
+            github.issues.createComment({
+              issue_number: context.issue.number,
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              body: output
+            })
+          }
+
+    - name: Terraform Plan Status
+      if: steps.plan.outcome == 'failure'
+      run: exit 1
+
+
+    - name: Terraform Apply
+      if: github.ref == 'refs/heads/main' && github.event_name == 'push'
+      run: terraform apply -auto-approve ${{ steps.tfvars.outputs.vars }}
+

.github/workflows/ci.yml 🔗

@@ -0,0 +1,33 @@
+name: CI
+
+on:
+  push:
+    branches: [ main ]
+  pull_request:
+
+jobs:
+
+  build:
+    strategy:
+      matrix:
+        go-version: [~1.16]
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v2
+
+    # Remove this later
+    - name: Clone internal repositories
+      run: |
+        git clone -b release https://${{ secrets.ACCESS_TOKEN }}@github.com/charmbracelet/charm-internal ../charm
+        git clone -b master https://${{ secrets.ACCESS_TOKEN }}@github.com/charmbracelet/bubbletea-internal ../bubbletea
+
+    - name: Set up Go
+      uses: actions/setup-go@v2
+      with:
+        go-version: ${{ matrix.go-version }}
+
+    - name: Build
+      run: go build -v ./...
+
+    - name: Test
+      run: go test -v ./...

.gitignore 🔗

@@ -1,3 +1,7 @@
 smoothie
 .ssh
 .repos
+dist
+.terraform*
+*.tfstate*
+*auto.tfvars

.goreleaser.yml 🔗

@@ -0,0 +1,37 @@
+project_name: smoothie
+
+env:
+  - GO111MODULE=on
+  - CGO_ENABLED=0
+
+before:
+  hooks:
+    - go mod download
+
+builds:
+  - id: "smoothie"
+    binary: "smoothie"
+    ldflags: -s -w -X main.Version={{ .Commit }}-snapshot -X main.CommitSHA={{ .Commit }}
+    goos:
+      - linux
+    goarch:
+      - amd64
+
+dockers:
+  - image_templates:
+      - "ghcr.io/charmbracelet/smoothie:snapshot"
+      - "ghcr.io/charmbracelet/smoothie:{{ .Commit }}-snapshot"
+    ids: [smoothie]
+    goarch: amd64
+    build_flag_templates:
+      - --platform=linux/amd64
+      - --label=org.opencontainers.image.title={{ .ProjectName }}
+      - --label=org.opencontainers.image.description={{ .ProjectName }}
+      - --label=org.opencontainers.image.url=https://github.com/charmbracelet/smoothie
+      - --label=org.opencontainers.image.source=https://github.com/charmbracelet/smoothie
+      - --label=org.opencontainers.image.version={{ .Commit }}-snapshot
+      - --label=org.opencontainers.image.created={{ .Date }}
+      - --label=org.opencontainers.image.revision={{ .FullCommit }}
+      - --label=org.opencontainers.image.licenses=MIT
+    dockerfile: Dockerfile
+    use: buildx

Dockerfile 🔗

@@ -0,0 +1,22 @@
+FROM alpine:latest
+
+RUN apk update && apk add --update nfs-utils git && rm -rf /var/cache/apk/*
+
+COPY smoothie /usr/local/bin/smoothie
+
+# Create directories
+WORKDIR /smoothie
+# Expose data volume
+VOLUME /smoothie
+
+# Environment variables
+ENV SMOOTHIE_KEY_PATH "/smoothie/ssh/smoothie_server_ed25519"
+ENV SMOOTHIE_REPO_KEYS_PATH "/smoothie/ssh/smoothie_git_authorized_keys"
+ENV SMOOTHIE_REPO_PATH "/smoothie/repos"
+
+# Expose ports
+# SSH
+EXPOSE 23231/tcp
+
+# Set the default command
+ENTRYPOINT [ "/usr/local/bin/smoothie" ]

main.tf 🔗

@@ -0,0 +1,39 @@
+terraform {
+  backend "s3" {
+    bucket = "charm-terraform-state"
+    key    = "smoothie-development"
+    region = "us-east-1"
+  }
+}
+
+variable "environment" {
+  default = "development"
+}
+
+variable "aws_region" {
+  default = "us-east-1"
+}
+
+variable "app_image" {
+  default = "ghcr.io/charmbracelet/smoothie:snapshot"
+}
+
+variable "force_new_deployment" {
+  default = false
+}
+
+module "smoothie" {
+  # source = "../terraform-aws-smoothie"
+  source  = "app.terraform.io/charm/smoothie/aws"
+  version = "0.1.2"
+
+  environment                  = var.environment
+  aws_region                   = var.aws_region
+  ecs_task_execution_role_name = "smoothieEcsTaskExecutionRole-${var.environment}"
+  app_image                    = var.app_image
+  app_count                    = 2
+  app_ssh_port                 = 23231
+  fargate_cpu                  = "1024"
+  fargate_memory               = "2048"
+  force_new_deployment         = var.force_new_deployment
+}