Files
coder/.github/workflows/pr-deploy.yaml
T
Ben Potter 34dfbfa9d3 ci: add DB to PR deploys (#8770)
* ci: add DB to PR deploys

* add bitnami remo

* fix namespace

* change ingress host

* remove wildcard host
2023-07-27 14:50:53 +00:00

357 lines
14 KiB
YAML

# This action will trigger when a PR is commented on with `/deploy-pr` or when the workflow is manually triggered.
name: Deploy PR
on:
issue_comment:
types: [created, edited]
workflow_dispatch:
inputs:
pr_number:
description: "PR number"
required: true
skip_build:
description: "Skip build job"
required: false
default: false
env:
REPO: ghcr.io/coder/coder-preview
permissions:
contents: read
packages: write
pull-requests: write
concurrency:
group: ${{ github.workflow }}-${{ github.event.issue.number || github.run_id }}
cancel-in-progress: false
jobs:
pr_commented:
if: (github.event_name == 'issue_comment' && contains(github.event.comment.body, '/deploy-pr') && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'COLLABORATOR' || github.event.comment.author_association == 'OWNER')) || github.event_name == 'workflow_dispatch'
outputs:
PR_NUMBER: ${{ steps.pr_info.outputs.PR_NUMBER }}
PR_TITLE: ${{ steps.pr_info.outputs.PR_TITLE }}
PR_URL: ${{ steps.pr_info.outputs.PR_URL }}
PR_BRANCH: ${{ steps.pr_info.outputs.PR_BRANCH }}
CODER_BASE_IMAGE_TAG: ${{ steps.set_tags.outputs.CODER_BASE_IMAGE_TAG }}
CODER_IMAGE_TAG: ${{ steps.set_tags.outputs.CODER_IMAGE_TAG }}
runs-on: "ubuntu-latest"
steps:
- name: Get PR number, title, and branch name
id: pr_info
run: |
set -euxo pipefail
if [[ ${{ github.event_name }} == "workflow_dispatch" ]]; then
PR_NUMBER=${{ github.event.inputs.pr_number }}
else
PR_NUMBER=${{ github.event.issue.number }}
fi
PR_TITLE=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/repos/coder/coder/pulls/$PR_NUMBER | jq -r '.title')
PR_BRANCH=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/repos/coder/coder/pulls/$PR_NUMBER | jq -r '.head.ref')
echo "PR_URL=https://github.com/coder/coder/pull/$PR_NUMBER" >> $GITHUB_OUTPUT
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_OUTPUT
echo "PR_TITLE=$PR_TITLE" >> $GITHUB_OUTPUT
echo "PR_BRANCH=$PR_BRANCH" >> $GITHUB_OUTPUT
- name: Set required tags
id: set_tags
run: |
set -euxo pipefail
echo "CODER_BASE_IMAGE_TAG=$CODER_BASE_IMAGE_TAG" >> $GITHUB_OUTPUT
echo "CODER_IMAGE_TAG=$CODER_IMAGE_TAG" >> $GITHUB_OUTPUT
env:
CODER_BASE_IMAGE_TAG: ghcr.io/coder/coder-preview-base:pr${{ steps.pr_info.outputs.PR_NUMBER }}
CODER_IMAGE_TAG: ghcr.io/coder/coder-preview:pr${{ steps.pr_info.outputs.PR_NUMBER }}
- name: Comment on PR
id: comment_id
if: github.event_name == 'issue_comment'
uses: peter-evans/create-or-update-comment@v3
with:
issue-number: ${{ steps.pr_info.outputs.PR_NUMBER }}
body: |
:rocket: Deploying PR ${{ steps.pr_info.outputs.PR_NUMBER }} ...
:warning: This deployment will be deleted when the PR is closed.
reactions: "+1"
build:
needs: pr_commented
# Skips the build job if the workflow was triggered by a workflow_dispatch event and the skip_build input is set to true
# or if the workflow was triggered by an issue_comment event and the comment body contains --skip-build
if: (github.event_name == 'workflow_dispatch' && github.event.inputs.skip_build == 'false') || (github.event_name == 'issue_comment' && contains(github.event.comment.body, '--skip-build') != true)
runs-on: ${{ github.repository_owner == 'coder' && 'buildjet-8vcpu-ubuntu-2204' || 'ubuntu-latest' }}
env:
DOCKER_CLI_EXPERIMENTAL: "enabled"
CODER_IMAGE_TAG: ${{ needs.pr_commented.outputs.CODER_IMAGE_TAG }}
PR_NUMBER: ${{ needs.pr_commented.outputs.PR_NUMBER }}
PR_BRANCH: ${{ needs.pr_commented.outputs.PR_BRANCH }}
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: ${{ env.PR_BRANCH }}
fetch-depth: 0
- name: Setup Node
uses: ./.github/actions/setup-node
- name: Setup Go
uses: ./.github/actions/setup-go
- name: Setup sqlc
uses: ./.github/actions/setup-sqlc
- name: GHCR Login
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Linux amd64 Docker image
run: |
set -euxo pipefail
go mod download
make gen/mark-fresh
export DOCKER_IMAGE_NO_PREREQUISITES=true
version="$(./scripts/version.sh)"
export CODER_IMAGE_BUILD_BASE_TAG="$(CODER_IMAGE_BASE=coder-base ./scripts/image_tag.sh --version "$version")"
make -j build/coder_linux_amd64
./scripts/build_docker.sh \
--arch amd64 \
--target ${{ env.CODER_IMAGE_TAG }} \
--version $version \
--push \
build/coder_linux_amd64
deploy:
needs: [build, pr_commented]
# Run deploy job only if build job was successful or skipped
if: always() && (needs.build.result == 'success' || needs.build.result == 'skipped') && needs.pr_commented.result == 'success'
runs-on: "ubuntu-latest"
env:
CODER_IMAGE_TAG: ${{ needs.pr_commented.outputs.CODER_IMAGE_TAG }}
PR_NUMBER: ${{ needs.pr_commented.outputs.PR_NUMBER }}
PR_TITLE: ${{ needs.pr_commented.outputs.PR_TITLE }}
PR_URL: ${{ needs.pr_commented.outputs.PR_URL }}
PR_BRANCH: ${{ needs.pr_commented.outputs.PR_BRANCH }}
PR_DEPLOYMENT_ACCESS_URL: "https://pr${{ needs.pr_commented.outputs.PR_NUMBER }}.${{ secrets.PR_DEPLOYMENTS_DOMAIN }}"
steps:
- name: Check if image exists
run: |
set -euxo pipefail
foundTag=$(curl -fsSL https://github.com/coder/coder/pkgs/container/coder-preview | grep -o ${{ env.CODER_IMAGE_TAG }} | head -n 1)
if [ -z "$foundTag" ]; then
echo "Image not found"
echo "${{ env.CODER_IMAGE_TAG }} not found in ghcr.io/coder/coder-preview"
echo "Please remove --skip-build from the comment or ./scripts/deploy-pr.sh"
exit 1
fi
- name: Checkout
uses: actions/checkout@v3
with:
ref: ${{ env.PR_BRANCH }}
- name: Set up kubeconfig
run: |
set -euxo pipefail
mkdir -p ~/.kube
echo "${{ secrets.PR_DEPLOYMENTS_KUBECONFIG }}" > ~/.kube/config
export KUBECONFIG=~/.kube/config
- name: Create PR namespace
run: |
set -euxo pipefail
# try to delete the namespace, but don't fail if it doesn't exist
kubectl delete namespace "pr${{ env.PR_NUMBER }}" || true
kubectl create namespace "pr${{ env.PR_NUMBER }}"
- name: Setup ingress
run: |
cat <<EOF > ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: pr${{ env.PR_NUMBER }}
namespace: pr${{ env.PR_NUMBER }}
annotations:
cert-manager.io/cluster-issuer: letsencrypt
spec:
tls:
- hosts:
- "${{ secrets.PR_DEPLOYMENTS_DOMAIN }}"
- "*.${{ secrets.PR_DEPLOYMENTS_DOMAIN }}"
secretName: pr${{ env.PR_NUMBER }}-tls
rules:
- host: "pr${{ env.PR_NUMBER }}.${{ secrets.PR_DEPLOYMENTS_DOMAIN }}"
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: coder
port:
number: 80
EOF
kubectl apply -f ingress.yaml
- name: Set up PostgreSQL database
run: |
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install coder-db bitnami/postgresql \
--namespace pr${{ env.PR_NUMBER }} \
--set auth.username=coder \
--set auth.password=coder \
--set auth.database=coder \
--set persistence.size=10Gi
kubectl create secret generic coder-db-url -n pr${{ env.PR_NUMBER }} \
--from-literal=url="postgres://coder:coder@coder-db-postgresql.pr${{ env.PR_NUMBER }}.svc.cluster.local:5432/coder?sslmode=disable"
- name: Create values.yaml
run: |
cat <<EOF > pr-deploy-values.yaml
coder:
image:
repo: ${{ env.REPO }}
tag: pr${{ env.PR_NUMBER }}
pullPolicy: Always
service:
type: ClusterIP
env:
- name: "CODER_ACCESS_URL"
value: "https://pr${{ env.PR_NUMBER }}.${{ secrets.PR_DEPLOYMENTS_DOMAIN }}"
- name: "CODER_WILDCARD_ACCESS_URL"
value: "*--pr${{ env.PR_NUMBER }}.${{ secrets.PR_DEPLOYMENTS_DOMAIN }}"
- name: "CODER_EXPERIMENTS"
value: "*"
- name: CODER_PG_CONNECTION_URL
valueFrom:
secretKeyRef:
name: coder-db-url
key: url
- name: "CODER_OAUTH2_GITHUB_ALLOW_SIGNUPS"
value: "true"
- name: "CODER_OAUTH2_GITHUB_CLIENT_ID"
value: "${{ secrets.PR_DEPLOYMENTS_GITHUB_OAUTH_CLIENT_ID }}"
- name: "CODER_OAUTH2_GITHUB_CLIENT_SECRET"
value: "${{ secrets.PR_DEPLOYMENTS_GITHUB_OAUTH_CLIENT_SECRET }}"
- name: "CODER_OAUTH2_GITHUB_ALLOWED_ORGS"
value: "coder"
EOF
- name: Install Helm chart
run: |
helm upgrade --install "pr${{ env.PR_NUMBER }}" ./helm \
--namespace "pr${{ env.PR_NUMBER }}" \
--values ./pr-deploy-values.yaml \
--force
- name: Install coder-logstream-kube
run: |
helm repo add coder-logstream-kube https://helm.coder.com/logstream-kube
helm upgrade --install coder-logstream-kube coder-logstream-kube/coder-logstream-kube \
--namespace "pr${{ env.PR_NUMBER }}" \
--set url="https://pr${{ env.PR_NUMBER }}.${{ secrets.PR_DEPLOYMENTS_DOMAIN }}"
- name: Get Coder binary
run: |
set -euxo pipefail
DEST="${HOME}/coder"
URL="${{ env.PR_DEPLOYMENT_ACCESS_URL }}/bin/coder-linux-amd64"
mkdir -p "$(dirname ${DEST})"
COUNT=0
until $(curl --output /dev/null --silent --head --fail "$URL"); do
printf '.'
sleep 5
COUNT=$((COUNT+1))
if [ $COUNT -ge 60 ]; then
echo "Timed out waiting for URL to be available"
exit 1
fi
done
curl -fsSL "$URL" -o "${DEST}"
chmod +x "${DEST}"
"${DEST}" version
- name: Create first user, template and workspace
id: setup_deployment
run: |
set -euxo pipefail
# Create first user
# create a masked random password 12 characters long
password=$(openssl rand -base64 16 | tr -d "=+/" | cut -c1-12)
# add mask so that the password is not printed to the logs
echo "::add-mask::$password"
echo "password=$password" >> $GITHUB_OUTPUT
/home/runner/coder login \
--first-user-username pr${{ env.PR_NUMBER }} \
--first-user-email ${{ env.PR_NUMBER }}@coder.com \
--first-user-password $password \
--first-user-trial \
--use-token-as-session \
${{ env.PR_DEPLOYMENT_ACCESS_URL }}
# Create template
/home/runner/coder templates init --id kubernetes && cd ./kubernetes/ && /home/runner/coder templates create -y --variable namespace=pr${{ env.PR_NUMBER }}
# Create workspace
cat <<EOF > workspace.yaml
cpu: "2"
memory: "4"
home_disk_size: "2"
EOF
/home/runner/coder create --template="kubernetes" pr${{ env.PR_NUMBER }} --rich-parameter-file ./workspace.yaml -y
/home/runner/coder stop pr${{ env.PR_NUMBER }} -y
- name: Send Slack notification
run: |
curl -s -o /dev/null -X POST -H 'Content-type: application/json' \
-d \
'{
"pr_number": "'"${{ env.PR_NUMBER }}"'",
"pr_url": "'"${{ env.PR_URL }}"'",
"pr_title": "'"${{ env.PR_TITLE }}"'",
"pr_access_url": "'"${{ env.PR_DEPLOYMENT_ACCESS_URL }}"'",
"pr_username": "'"pr${{ env.PR_NUMBER }}"'",
"pr_email": "'"${{ env.PR_NUMBER }}@coder.com"'",
"pr_password": "'"${{ steps.setup_deployment.outputs.password }}"'",
"pr_actor": "'"${{ github.actor }}"'"
}' \
${{ secrets.PR_DEPLOYMENTS_SLACK_WEBHOOK }}
echo "Slack notification sent"
- name: Find Comment
uses: peter-evans/find-comment@v2
if: github.event_name == 'issue_comment'
id: fc
with:
issue-number: ${{ env.PR_NUMBER }}
comment-author: "github-actions[bot]"
body-includes: This deployment will be deleted when the PR is closed
direction: last
- name: Comment on PR
uses: peter-evans/create-or-update-comment@v3
if: github.event_name == 'issue_comment'
with:
issue-number: ${{ env.PR_NUMBER }}
edit-mode: replace
comment-id: ${{ steps.fc.outputs.comment-id }}
body: |
:heavy_check_mark: Deployed PR ${{ env.PR_NUMBER }} successfully.
:rocket: Access the deployment link [here](${{ env.PR_DEPLOYMENT_ACCESS_URL }}).
:warning: This deployment will be deleted when the PR is closed.
reactions: rocket