|
13 | 13 | - labeled
|
14 | 14 | workflow_dispatch:
|
15 | 15 | inputs:
|
16 |
| - deploy: |
17 |
| - description: "Deploy location" |
18 |
| - required: true |
19 |
| - default: "none" |
20 |
| - type: choice |
21 |
| - options: |
22 |
| - - production |
23 |
| - - staging |
24 |
| - - none |
| 16 | + deploy: |
| 17 | + description: "Which environment to deploy to" |
| 18 | + required: true |
| 19 | + default: "none" |
| 20 | + type: choice |
| 21 | + options: |
| 22 | + - prod |
| 23 | + - test |
25 | 24 |
|
26 | 25 | concurrency:
|
27 | 26 | group: ${{ github.workflow }}-${{ github.ref }}
|
28 | 27 | cancel-in-progress: true
|
29 | 28 |
|
| 29 | +env: |
| 30 | + DEPLOY: ${{ (inputs.deploy != 'none' && inputs.deploy) || ((github.event_name == 'workflow_dispatch' && github.event.inputs.deploy == 'prod') || (github.event_name == 'push' && github.ref_type == 'branch' && github.ref_name == 'master') && 'prod') || ((github.event_name == 'workflow_dispatch' && github.event.inputs.deploy == 'stage') || (github.event_name == 'push' && github.ref_type == 'branch' && github.ref_name == 'dev') || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'staged')) && 'test') || 'none' }} |
| 31 | + |
30 | 32 | jobs:
|
31 | 33 | ci:
|
32 | 34 | name: Website Lint, Build, Test, Deploy
|
33 | 35 | runs-on: aws-runner
|
34 |
| - env: |
35 |
| - DEPLOY_PROD: ${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy == 'production') || (github.event_name == 'push' && github.ref_type == 'branch' && github.ref_name == 'master') }} |
36 |
| - DEPLOY_STAGE: ${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy == 'staging') || (github.event_name == 'push' && github.ref_type == 'branch' && github.ref_name == 'dev') || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'staged')) }} |
37 | 36 | concurrency:
|
38 | 37 | group: ${{ github.workflow }}-ci-${{ github.ref }}
|
39 | 38 | cancel-in-progress: true
|
@@ -89,62 +88,158 @@ jobs:
|
89 | 88 | run: make lint
|
90 | 89 |
|
91 | 90 | - name: Build App
|
92 |
| - run: make ${{ (env.DEPLOY_PROD == 'true' && 'prod') || 'stage' }} |
93 |
| - |
94 |
| - - name: Configure AWS credentials |
95 |
| - uses: aws-actions/configure-aws-credentials@v1 |
96 |
| - if: ${{ env.DEPLOY_PROD == 'true' || env.DEPLOY_STAGE == 'true' }} |
97 |
| - with: |
98 |
| - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} |
99 |
| - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} |
100 |
| - aws-region: ${{ secrets.AWS_REGION }} |
101 |
| - |
102 |
| - - name: Login to Amazon ECR |
103 |
| - id: login-ecr |
104 |
| - if: ${{ env.DEPLOY_PROD == 'true' || env.DEPLOY_STAGE == 'true' }} |
105 |
| - uses: aws-actions/amazon-ecr-login@v1 |
| 91 | + run: make ${{ env.DEPLOY != 'none' && env.DEPLOY || 'stage' }} |
106 | 92 |
|
107 | 93 | - name: Make build context
|
108 |
| - if: ${{ env.DEPLOY_PROD == 'true' || env.DEPLOY_STAGE == 'true' }} |
| 94 | + if: env.DEPLOY != 'none' |
109 | 95 | run: |
|
110 | 96 | docker context create builders
|
111 | 97 |
|
112 | 98 | - name: Setup buildx
|
113 | 99 | uses: docker/setup-buildx-action@v2
|
114 |
| - if: ${{ env.DEPLOY_PROD == 'true' || env.DEPLOY_STAGE == 'true' }} |
| 100 | + if: env.DEPLOY != 'none' |
115 | 101 | with:
|
116 | 102 | install: true
|
117 | 103 | endpoint: builders
|
118 | 104 |
|
| 105 | + - name: Login to GitHub Container Registry |
| 106 | + uses: docker/login-action@v2 |
| 107 | + if: env.DEPLOY != 'none' |
| 108 | + with: |
| 109 | + registry: ghcr.io |
| 110 | + username: ${{ github.actor }} |
| 111 | + password: ${{ github.token }} |
| 112 | + |
119 | 113 | - name: Build docker image
|
120 |
| - uses: docker/build-push-action@v3 |
121 |
| - if: ${{ env.DEPLOY_PROD == 'true' || env.DEPLOY_STAGE == 'true' }} |
| 114 | + uses: docker/build-push-action@v4 |
| 115 | + if: env.DEPLOY != 'none' |
122 | 116 | with:
|
123 | 117 | context: .
|
124 | 118 | file: docker/partial.Dockerfile
|
125 | 119 | tags: |
|
126 |
| - ${{ steps.login-ecr.outputs.registry }}/${{ (env.DEPLOY_PROD == 'true' && '7tv') || '7tv-stage' }}/website:latest |
127 |
| - ${{ steps.login-ecr.outputs.registry }}/${{ (env.DEPLOY_PROD == 'true' && '7tv') || '7tv-stage' }}/website:${{ github.sha }} |
| 120 | + ghcr.io/seventv/website:${{ env.DEPLOY }}-${{ github.sha }} |
| 121 | + ghcr.io/seventv/website:${{ env.DEPLOY }}-latest |
128 | 122 | push: true
|
129 | 123 |
|
130 |
| - - name: Update deployment template |
131 |
| - uses: danielr1996/[email protected] |
132 |
| - if: ${{ env.DEPLOY_PROD == 'true' || env.DEPLOY_STAGE == 'true' }} |
133 |
| - env: |
134 |
| - IMAGE: ${{ steps.login-ecr.outputs.registry }}/${{ (env.DEPLOY_PROD == 'true' && '7tv') || '7tv-stage' }}/website:${{ github.sha }} |
135 |
| - with: |
136 |
| - input: k8s/${{ (env.DEPLOY_PROD == 'true' && 'production') || 'staging' }}.template.yaml |
137 |
| - output: k8s/deploy.yaml |
| 124 | + validate: |
| 125 | + name: API Deploy Validation |
| 126 | + needs: ci |
| 127 | + runs-on: ubuntu-latest |
| 128 | + permissions: |
| 129 | + pull-requests: write |
| 130 | + defaults: |
| 131 | + run: |
| 132 | + working-directory: ./terraform |
138 | 133 |
|
139 |
| - - name: Setup Kubectl |
140 |
| - |
| 134 | + steps: |
| 135 | + - name: Checkout code |
| 136 | + id: ok |
| 137 | + if: env.DEPLOY != 'none' |
| 138 | + uses: actions/checkout@v3 |
| 139 | + |
| 140 | + - name: "Setup Terraform" |
| 141 | + if: steps.ok.outcome == 'success' |
| 142 | + uses: hashicorp/setup-terraform@v1 |
| 143 | + with: |
| 144 | + cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }} |
141 | 145 |
|
142 |
| - - name: Deploy to k8s |
143 |
| - if: ${{ env.DEPLOY_PROD == 'true' || env.DEPLOY_STAGE == 'true' }} |
| 146 | + - name: "Terraform Init" |
| 147 | + if: steps.ok.outcome == 'success' |
| 148 | + id: init |
144 | 149 | env:
|
145 |
| - KUBE_CONFIG_DATA: ${{ (env.DEPLOY_PROD == 'true' && secrets.KUBECONFIG) || secrets.KUBECONFIG_STAGE }} |
| 150 | + TF_WORKSPACE: ${{ env.DEPLOY }} |
| 151 | + run: terraform init |
| 152 | + continue-on-error: true |
| 153 | + |
| 154 | + - name: "Terraform Workspace" |
| 155 | + if: steps.ok.outcome == 'success' |
| 156 | + run: terraform workspace select -or-create=true ${{ env.DEPLOY }} |
| 157 | + |
| 158 | + - name: Terraform fmt |
| 159 | + if: steps.ok.outcome == 'success' |
| 160 | + id: fmt |
| 161 | + run: terraform fmt -check |
| 162 | + continue-on-error: true |
| 163 | + |
| 164 | + - name: Terraform Validate |
| 165 | + if: steps.ok.outcome == 'success' |
| 166 | + id: validate |
| 167 | + run: terraform validate -no-color |
| 168 | + |
| 169 | + - name: Terraform Variables |
| 170 | + if: steps.ok.outcome == 'success' |
146 | 171 | run: |
|
147 |
| - mkdir -p ~/.kube |
148 |
| - (echo $KUBE_CONFIG_DATA | base64 -d) >> ~/.kube/config |
| 172 | + cat <<EOF > *.auto.tfvars |
| 173 | + image_url="ghcr.io/seventv/website:${{ env.DEPLOY }}-${{ github.sha }}" |
| 174 | + image_pull_policy="IfNotPresent" |
149 | 175 |
|
150 |
| - kubectl apply -f k8s/deploy.yaml |
| 176 | + EOF |
| 177 | +
|
| 178 | + - name: "Terraform Plan" |
| 179 | + if: steps.ok.outcome == 'success' |
| 180 | + id: plan |
| 181 | + run: terraform plan -no-color |
| 182 | + |
| 183 | + - uses: actions/github-script@v6 |
| 184 | + if: steps.ok.outcome == 'success' && github.event_name == 'pull_request' |
| 185 | + env: |
| 186 | + PLAN: "terraform\n${{ steps.plan.outputs.stdout }}" |
| 187 | + with: |
| 188 | + github-token: ${{ secrets.GITHUB_TOKEN }} |
| 189 | + script: | |
| 190 | + // 1. Retrieve existing bot comments for the PR |
| 191 | + const { data: comments } = await github.rest.issues.listComments({ |
| 192 | + owner: context.repo.owner, |
| 193 | + repo: context.repo.repo, |
| 194 | + issue_number: context.issue.number, |
| 195 | + }) |
| 196 | + const botComment = comments.find(comment => { |
| 197 | + return comment.user.type === 'Bot' && comment.body.includes('Terraform Format and Style') |
| 198 | + }) |
| 199 | +
|
| 200 | + // 2. Prepare format of the comment |
| 201 | + const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\` |
| 202 | + #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\` |
| 203 | + #### Terraform Validation 🤖\`${{ steps.validate.outcome }}\` |
| 204 | + <details><summary>Validation Output</summary> |
| 205 | +
|
| 206 | + \`\`\`\n |
| 207 | + ${{ steps.validate.outputs.stdout }} |
| 208 | + \`\`\` |
| 209 | +
|
| 210 | + </details> |
| 211 | +
|
| 212 | + #### Terraform Plan 📖\`${{ steps.plan.outcome }}\` |
| 213 | +
|
| 214 | + <details><summary>Show Plan</summary> |
| 215 | +
|
| 216 | + \`\`\`\n |
| 217 | + ${process.env.PLAN} |
| 218 | + \`\`\` |
| 219 | +
|
| 220 | + </details> |
| 221 | +
|
| 222 | + *Actor: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Workflow: \`${{ github.workflow }}\`*`; |
| 223 | +
|
| 224 | + // 3. If we have a comment, update it, otherwise create a new one |
| 225 | + if (botComment) { |
| 226 | + github.rest.issues.updateComment({ |
| 227 | + owner: context.repo.owner, |
| 228 | + repo: context.repo.repo, |
| 229 | + comment_id: botComment.id, |
| 230 | + body: output |
| 231 | + }) |
| 232 | + } else { |
| 233 | + github.rest.issues.createComment({ |
| 234 | + issue_number: context.issue.number, |
| 235 | + owner: context.repo.owner, |
| 236 | + repo: context.repo.repo, |
| 237 | + body: output |
| 238 | + }) |
| 239 | + } |
| 240 | +
|
| 241 | + - name: "Terraform Apply" |
| 242 | + if: steps.ok.outcome == 'success' |
| 243 | + id: apply |
| 244 | + run: terraform apply -no-color -auto-approve |
| 245 | + continue-on-error: true |
0 commit comments