From 114ad4624e2408c19f87bdb7afd4210a785b966a Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Fri, 11 Aug 2023 13:49:23 +0300 Subject: [PATCH] ci: upgrade pr deployments workflow (#8924) --- .github/pr-deployments/certificate.yaml | 13 + .github/pr-deployments/rbac.yaml | 31 +++ .github/pr-deployments/template/main.tf | 313 +++++++++++++++++++++ .github/pr-deployments/values.yaml | 38 +++ .github/workflows/pr-deploy.yaml | 347 +++++++++++------------- docs/CONTRIBUTING.md | 5 +- docs/images/deploy-pr-manually.png | Bin 0 -> 13583 bytes docs/images/pr-deploy-manual.png | Bin 27155 -> 0 bytes scripts/deploy-pr.sh | 35 +-- 9 files changed, 575 insertions(+), 207 deletions(-) create mode 100644 .github/pr-deployments/certificate.yaml create mode 100644 .github/pr-deployments/rbac.yaml create mode 100644 .github/pr-deployments/template/main.tf create mode 100644 .github/pr-deployments/values.yaml create mode 100644 docs/images/deploy-pr-manually.png delete mode 100644 docs/images/pr-deploy-manual.png diff --git a/.github/pr-deployments/certificate.yaml b/.github/pr-deployments/certificate.yaml new file mode 100644 index 0000000000..cf441a98bb --- /dev/null +++ b/.github/pr-deployments/certificate.yaml @@ -0,0 +1,13 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: pr${PR_NUMBER}-tls + namespace: pr-deployment-certs +spec: + secretName: pr${PR_NUMBER}-tls + issuerRef: + name: letsencrypt + kind: ClusterIssuer + dnsNames: + - "${PR_HOSTNAME}" + - "*.${PR_HOSTNAME}" diff --git a/.github/pr-deployments/rbac.yaml b/.github/pr-deployments/rbac.yaml new file mode 100644 index 0000000000..0d37cae7da --- /dev/null +++ b/.github/pr-deployments/rbac.yaml @@ -0,0 +1,31 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: coder-workspace-pr${PR_NUMBER} + namespace: pr${PR_NUMBER} + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: coder-workspace-pr${PR_NUMBER} + namespace: pr${PR_NUMBER} +rules: + - apiGroups: ["*"] + resources: ["*"] + verbs: ["*"] + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: coder-workspace-pr${PR_NUMBER} + namespace: pr${PR_NUMBER} +subjects: + - kind: ServiceAccount + name: coder-workspace-pr${PR_NUMBER} + namespace: pr${PR_NUMBER} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: coder-workspace-pr${PR_NUMBER} diff --git a/.github/pr-deployments/template/main.tf b/.github/pr-deployments/template/main.tf new file mode 100644 index 0000000000..bef767547b --- /dev/null +++ b/.github/pr-deployments/template/main.tf @@ -0,0 +1,313 @@ +terraform { + required_providers { + coder = { + source = "coder/coder" + version = "~> 0.11.0" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = "~> 2.22" + } + } +} + +provider "coder" { +} + +variable "namespace" { + type = string + description = "The Kubernetes namespace to create workspaces in (must exist prior to creating workspaces)" +} + +data "coder_parameter" "cpu" { + name = "cpu" + display_name = "CPU" + description = "The number of CPU cores" + default = "2" + icon = "/icon/memory.svg" + mutable = true + option { + name = "2 Cores" + value = "2" + } + option { + name = "4 Cores" + value = "4" + } + option { + name = "6 Cores" + value = "6" + } + option { + name = "8 Cores" + value = "8" + } +} + +data "coder_parameter" "memory" { + name = "memory" + display_name = "Memory" + description = "The amount of memory in GB" + default = "2" + icon = "/icon/memory.svg" + mutable = true + option { + name = "2 GB" + value = "2" + } + option { + name = "4 GB" + value = "4" + } + option { + name = "6 GB" + value = "6" + } + option { + name = "8 GB" + value = "8" + } +} + +data "coder_parameter" "home_disk_size" { + name = "home_disk_size" + display_name = "Home disk size" + description = "The size of the home disk in GB" + default = "10" + type = "number" + icon = "/emojis/1f4be.png" + mutable = false + validation { + min = 1 + max = 99999 + } +} + +provider "kubernetes" { + config_path = null +} + +data "coder_workspace" "me" {} + +resource "coder_agent" "main" { + os = "linux" + arch = "amd64" + startup_script_timeout = 180 + startup_script = <<-EOT + set -e + + # install and start code-server + curl -fsSL https://code-server.dev/install.sh | sh -s -- --method=standalone --prefix=/tmp/code-server + /tmp/code-server/bin/code-server --auth none --port 13337 >/tmp/code-server.log 2>&1 & + + EOT + + # The following metadata blocks are optional. They are used to display + # information about your workspace in the dashboard. You can remove them + # if you don't want to display any information. + # For basic resources, you can use the `coder stat` command. + # If you need more control, you can write your own script. + metadata { + display_name = "CPU Usage" + key = "0_cpu_usage" + script = "coder stat cpu" + interval = 10 + timeout = 1 + } + + metadata { + display_name = "RAM Usage" + key = "1_ram_usage" + script = "coder stat mem" + interval = 10 + timeout = 1 + } + + metadata { + display_name = "Home Disk" + key = "3_home_disk" + script = "coder stat disk --path $${HOME}" + interval = 60 + timeout = 1 + } + + metadata { + display_name = "CPU Usage (Host)" + key = "4_cpu_usage_host" + script = "coder stat cpu --host" + interval = 10 + timeout = 1 + } + + metadata { + display_name = "Memory Usage (Host)" + key = "5_mem_usage_host" + script = "coder stat mem --host" + interval = 10 + timeout = 1 + } + + metadata { + display_name = "Load Average (Host)" + key = "6_load_host" + # get load avg scaled by number of cores + script = <> $GITHUB_OUTPUT + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + get_info: - if: github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request' + needs: check_pr + if: ${{ needs.check_pr.outputs.PR_OPEN == 'true' }} 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 }} - NEW: ${{ steps.check_deployment.outputs.new }} - BUILD: ${{ steps.filter.outputs.all_count > steps.filter.outputs.ignored_count || steps.check_deployment.outputs.new }} + NEW: ${{ steps.check_deployment.outputs.NEW }} + BUILD: ${{ steps.filter.outputs.all_count > steps.filter.outputs.ignored_count || steps.check_deployment.outputs.NEW }} runs-on: "ubuntu-latest" steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Get PR number, title, and branch name id: pr_info run: | - set -euxo pipefail - PR_NUMBER=${{ github.event.inputs.pr_number || github.event.pull_request.number }} - 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 + set -euo pipefail + PR_NUMBER=$(gh pr view --json number | jq -r '.number') + PR_TITLE=$(gh pr view --json title | jq -r '.title') + PR_URL=$(gh pr view --json url | jq -r '.url') + echo "PR_URL=$PR_URL" >> $GITHUB_OUTPUT echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_OUTPUT echo "PR_TITLE=$PR_TITLE" >> $GITHUB_OUTPUT - echo "PR_BRANCH=$PR_BRANCH" >> $GITHUB_OUTPUT + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Set required tags id: set_tags run: | - set -euxo pipefail + set -euo pipefail echo "CODER_BASE_IMAGE_TAG=$CODER_BASE_IMAGE_TAG" >> $GITHUB_OUTPUT echo "CODER_IMAGE_TAG=$CODER_IMAGE_TAG" >> $GITHUB_OUTPUT env: @@ -74,7 +105,7 @@ jobs: - name: Set up kubeconfig run: | - set -euxo pipefail + set -euo pipefail mkdir -p ~/.kube echo "${{ secrets.PR_DEPLOYMENTS_KUBECONFIG }}" > ~/.kube/config export KUBECONFIG=~/.kube/config @@ -82,53 +113,21 @@ jobs: - name: Check if the helm deployment already exists id: check_deployment run: | - set -euxo pipefail + set -euo pipefail if helm status "pr${{ steps.pr_info.outputs.PR_NUMBER }}" --namespace "pr${{ steps.pr_info.outputs.PR_NUMBER }}" > /dev/null 2>&1; then echo "Deployment already exists. Skipping deployment." - new=false + NEW=false else echo "Deployment doesn't exist." - new=true + NEW=true fi - echo "new=$new" >> $GITHUB_OUTPUT - - - name: Find Comment - uses: peter-evans/find-comment@v2 - if: github.event_name == 'workflow_dispatch' || steps.check_deployment.outputs.NEW == 'false' - id: fc - with: - issue-number: ${{ steps.pr_info.outputs.PR_NUMBER }} - comment-author: "github-actions[bot]" - body-includes: ":rocket:" - direction: last - - - name: Comment on PR - id: comment_id - if: github.event_name == 'workflow_dispatch' || steps.check_deployment.outputs.NEW == 'false' - uses: peter-evans/create-or-update-comment@v3 - with: - comment-id: ${{ steps.fc.outputs.comment-id }} - issue-number: ${{ steps.pr_info.outputs.PR_NUMBER }} - edit-mode: replace - body: | - --- - :rocket: Deploying PR ${{ steps.pr_info.outputs.PR_NUMBER }} ... - --- - reactions: eyes - reactions-edit-mode: replace - - - name: Checkout - if: github.event_name == 'workflow_dispatch' || steps.check_deployment.outputs.NEW == 'false' - uses: actions/checkout@v3 - with: - ref: ${{ steps.pr_info.outputs.PR_BRANCH }} - fetch-depth: 0 + echo "NEW=$NEW" >> $GITHUB_OUTPUT - name: Check changed files - if: github.event_name == 'workflow_dispatch' || steps.check_deployment.outputs.NEW == 'false' uses: dorny/paths-filter@v2 id: filter with: + base: ${{ github.ref }} filters: | all: - "**" @@ -149,47 +148,76 @@ jobs: - "scripts/**/*[^D][^o][^c][^k][^e][^r][^f][^i][^l][^e][.][b][^a][^s][^e]*" - name: Print number of changed files - if: github.event_name == 'workflow_dispatch' || steps.check_deployment.outputs.NEW == 'false' run: | - set -euxo pipefail + set -euo pipefail echo "Total number of changed files: ${{ steps.filter.outputs.all_count }}" echo "Number of ignored files: ${{ steps.filter.outputs.ignored_count }}" + - name: Print job outputs + run: | + set -euo pipefail + # Print all outputs of this job + echo "PR_NUMBER=${{ steps.pr_info.outputs.PR_NUMBER }}" + echo "PR_TITLE=${{ steps.pr_info.outputs.PR_TITLE }}" + echo "PR_URL=${{ steps.pr_info.outputs.PR_URL }}" + echo "CODER_BASE_IMAGE_TAG=${{ steps.set_tags.outputs.CODER_BASE_IMAGE_TAG }}" + echo "CODER_IMAGE_TAG=${{ steps.set_tags.outputs.CODER_IMAGE_TAG }}" + echo "NEW=${{ steps.check_deployment.outputs.NEW }}" + echo "BUILD=${{ steps.filter.outputs.all_count > steps.filter.outputs.ignored_count || steps.check_deployment.outputs.NEW || github.event.inputs.build == 'true' }}" + echo "GITHUB_REF=${{ github.ref }}" + + comment-pr: + needs: [check_pr, get_info] + if: ${{ needs.get_info.outputs.BUILD == 'true' || github.event.inputs.deploy == 'true' || github.event.inputs.build == 'true' }} + runs-on: "ubuntu-latest" + steps: + - name: Find Comment + uses: peter-evans/find-comment@v2 + id: fc + with: + issue-number: ${{ needs.get_info.outputs.PR_NUMBER }} + comment-author: "github-actions[bot]" + body-includes: ":rocket:" + direction: last + + - name: Comment on PR + id: comment_id + uses: peter-evans/create-or-update-comment@v3 + with: + comment-id: ${{ steps.fc.outputs.comment-id }} + issue-number: ${{ needs.get_info.outputs.PR_NUMBER }} + edit-mode: replace + body: | + --- + :rocket: Deploying PR ${{ needs.get_info.outputs.PR_NUMBER }} ... + --- + reactions: eyes + reactions-edit-mode: replace + build: needs: get_info - # 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 - # always run the build job if a pull_request event triggered the workflow - if: | - (github.event_name == 'workflow_dispatch' && github.event.inputs.skip_build == 'false') || - (github.event_name == 'pull_request' && needs.get_info.result == 'success' && needs.get_info.outputs.NEW == 'false') + # Run build job only if there are changes in the files that we care about or if the workflow is manually triggered with --build flag + if: needs.get_info.outputs.BUILD == 'true' runs-on: ${{ github.repository_owner == 'coder' && 'buildjet-8vcpu-ubuntu-2204' || 'ubuntu-latest' }} env: DOCKER_CLI_EXPERIMENTAL: "enabled" CODER_IMAGE_TAG: ${{ needs.get_info.outputs.CODER_IMAGE_TAG }} - PR_NUMBER: ${{ needs.get_info.outputs.PR_NUMBER }} - PR_BRANCH: ${{ needs.get_info.outputs.PR_BRANCH }} steps: - name: Checkout uses: actions/checkout@v3 with: - ref: ${{ env.PR_BRANCH }} fetch-depth: 0 - name: Setup Node - if: needs.get_info.outputs.BUILD == 'true' uses: ./.github/actions/setup-node - name: Setup Go - if: needs.get_info.outputs.BUILD == 'true' uses: ./.github/actions/setup-go - name: Setup sqlc - if: needs.get_info.outputs.BUILD == 'true' uses: ./.github/actions/setup-sqlc - name: GHCR Login - if: needs.get_info.outputs.BUILD == 'true' uses: docker/login-action@v2 with: registry: ghcr.io @@ -197,9 +225,8 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push Linux amd64 Docker image - if: needs.get_info.outputs.BUILD == 'true' run: | - set -euxo pipefail + set -euo pipefail go mod download make gen/mark-fresh export DOCKER_IMAGE_NO_PREREQUISITES=true @@ -217,35 +244,42 @@ jobs: needs: [build, get_info] # Run deploy job only if build job was successful or skipped if: | - always() && (needs.build.result == 'success' || needs.build.result == 'skipped') && - (github.event_name == 'workflow_dispatch' || needs.get_info.outputs.NEW == 'false') + always() && (needs.build.result == 'success' || needs.build.result == 'skipped') && + (needs.get_info.outputs.BUILD == 'true' || github.event.inputs.deploy == 'true') runs-on: "ubuntu-latest" env: CODER_IMAGE_TAG: ${{ needs.get_info.outputs.CODER_IMAGE_TAG }} PR_NUMBER: ${{ needs.get_info.outputs.PR_NUMBER }} PR_TITLE: ${{ needs.get_info.outputs.PR_TITLE }} PR_URL: ${{ needs.get_info.outputs.PR_URL }} - PR_BRANCH: ${{ needs.get_info.outputs.PR_BRANCH }} - PR_DEPLOYMENT_ACCESS_URL: "pr${{ needs.get_info.outputs.PR_NUMBER }}.${{ secrets.PR_DEPLOYMENTS_DOMAIN }}" + PR_HOSTNAME: "pr${{ needs.get_info.outputs.PR_NUMBER }}.${{ secrets.PR_DEPLOYMENTS_DOMAIN }}" steps: - name: Set up kubeconfig run: | - set -euxo pipefail + set -euo pipefail mkdir -p ~/.kube echo "${{ secrets.PR_DEPLOYMENTS_KUBECONFIG }}" > ~/.kube/config export KUBECONFIG=~/.kube/config - name: Check if image exists - if: needs.get_info.outputs.NEW == 'true' 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) + set -euo pipefail + foundTag=$( + gh api /orgs/coder/packages/container/coder-preview/versions | + jq -r --arg tag "pr${{ env.PR_NUMBER }}" '.[] | + select(.metadata.container.tags == [$tag]) | + .metadata.container.tags[0]' + ) 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 and try again" exit 1 + else + echo "Image found" + echo "$foundTag tag found in ghcr.io/coder/coder-preview" fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Add DNS record to Cloudflare if: needs.get_info.outputs.NEW == 'true' @@ -253,43 +287,27 @@ jobs: curl -X POST "https://api.cloudflare.com/client/v4/zones/${{ secrets.PR_DEPLOYMENTS_ZONE_ID }}/dns_records" \ -H "Authorization: Bearer ${{ secrets.PR_DEPLOYMENTS_CLOUDFLARE_API_TOKEN }}" \ -H "Content-Type:application/json" \ - --data '{"type":"CNAME","name":"*.${{ env.PR_DEPLOYMENT_ACCESS_URL }}","content":"${{ env.PR_DEPLOYMENT_ACCESS_URL }}","ttl":1,"proxied":false}' - - - name: Checkout - uses: actions/checkout@v3 - with: - ref: ${{ env.PR_BRANCH }} + --data '{"type":"CNAME","name":"*.${{ env.PR_HOSTNAME }}","content":"${{ env.PR_HOSTNAME }}","ttl":1,"proxied":false}' - name: Create PR namespace - if: needs.get_info.outputs.NEW == 'true' + if: needs.get_info.outputs.NEW == 'true' || github.event.inputs.deploy == 'true' run: | - set -euxo pipefail + set -euo 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: Checkout + uses: actions/checkout@v3 + - name: Check and Create Certificate - if: needs.get_info.outputs.NEW == 'true' + if: needs.get_info.outputs.NEW == 'true' || github.event.inputs.deploy == 'true' run: | # Using kubectl to check if a Certificate resource already exists # we are doing this to avoid letsenrypt rate limits if ! kubectl get certificate pr${{ env.PR_NUMBER }}-tls -n pr-deployment-certs > /dev/null 2>&1; then echo "Certificate doesn't exist. Creating a new one." - cat < pr-deploy-values.yaml - coder: - image: - repo: ${{ env.REPO }} - tag: pr${{ env.PR_NUMBER }} - pullPolicy: Always - service: - type: ClusterIP - ingress: - enable: true - className: traefik - host: ${{ env.PR_DEPLOYMENT_ACCESS_URL }} - wildcardHost: "*.${{ env.PR_DEPLOYMENT_ACCESS_URL }}" - tls: - enable: true - secretName: pr${{ env.PR_NUMBER }}-tls - wildcardSecretName: pr${{ env.PR_NUMBER }}-tls - env: - - name: "CODER_ACCESS_URL" - value: "https://${{ env.PR_DEPLOYMENT_ACCESS_URL }}" - - name: "CODER_WILDCARD_ACCESS_URL" - value: "*.${{ env.PR_DEPLOYMENT_ACCESS_URL }}" - - name: "CODER_EXPERIMENTS" - value: "${{ github.event.inputs.experiments }}" - - 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 + set -euo pipefail + # Create service account, role, rolebinding + envsubst < ./.github/pr-deployments/rbac.yaml | kubectl apply -f - + + - name: Create values.yaml + env: + EXPERIMENTS: ${{ github.event.inputs.experiments }} + PR_DEPLOYMENTS_GITHUB_OAUTH_CLIENT_ID: ${{ secrets.PR_DEPLOYMENTS_GITHUB_OAUTH_CLIENT_ID }} + PR_DEPLOYMENTS_GITHUB_OAUTH_CLIENT_SECRET: ${{ secrets.PR_DEPLOYMENTS_GITHUB_OAUTH_CLIENT_SECRET }} + run: | + set -euo pipefail + envsubst < ./.github/pr-deployments/values.yaml > ./pr-deploy-values.yaml - name: Install/Upgrade Helm chart run: | - set -euxo pipefail - if [[ ${{ github.event_name }} == "workflow_dispatch" ]]; then - helm upgrade --install "pr${{ env.PR_NUMBER }}" ./helm \ - --namespace "pr${{ env.PR_NUMBER }}" \ - --values ./pr-deploy-values.yaml \ - --force - else - if [[ ${{ needs.get_info.outputs.BUILD }} == "true" ]]; then - helm upgrade --install "pr${{ env.PR_NUMBER }}" ./helm \ - --namespace "pr${{ env.PR_NUMBER }}" \ - --reuse-values \ - --force - else - echo "Skipping helm upgrade, as there is no new image to deploy" - fi - fi + set -euo pipefail + helm upgrade --install "pr${{ env.PR_NUMBER }}" ./helm/coder \ + --namespace "pr${{ env.PR_NUMBER }}" \ + --values ./pr-deploy-values.yaml \ + --force - name: Install coder-logstream-kube - if: needs.get_info.outputs.NEW == 'true' + if: needs.get_info.outputs.NEW == 'true' || github.event.inputs.deploy == 'true' 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 }}" + --set url="https://${{ env.PR_HOSTNAME }}" - name: Get Coder binary - if: needs.get_info.outputs.NEW == 'true' + if: needs.get_info.outputs.NEW == 'true' || github.event.inputs.deploy == 'true' run: | - set -euxo pipefail + set -euo pipefail DEST="${HOME}/coder" - URL="https://${{ env.PR_DEPLOYMENT_ACCESS_URL }}/bin/coder-linux-amd64" + URL="https://${{ env.PR_HOSTNAME }}/bin/coder-linux-amd64" mkdir -p "$(dirname ${DEST})" @@ -414,10 +395,10 @@ jobs: mv "${DEST}" /usr/local/bin/coder - name: Create first user, template and workspace - if: needs.get_info.outputs.NEW == 'true' + if: needs.get_info.outputs.NEW == 'true' || github.event.inputs.deploy == 'true' id: setup_deployment run: | - set -euxo pipefail + set -euo pipefail # Create first user @@ -429,28 +410,24 @@ jobs: echo "password=$password" >> $GITHUB_OUTPUT coder login \ - --first-user-username test \ + --first-user-username coder \ --first-user-email pr${{ env.PR_NUMBER }}@coder.com \ --first-user-password $password \ --first-user-trial \ --use-token-as-session \ - https://${{ env.PR_DEPLOYMENT_ACCESS_URL }} + https://${{ env.PR_HOSTNAME }} # Create template - coder templates init --id kubernetes && cd ./kubernetes/ && coder templates create -y --variable namespace=pr${{ env.PR_NUMBER }} + cd ./.github/pr-deployments/template + terraform init + coder templates create -y --variable namespace=pr${{ env.PR_NUMBER }} kubernetes # Create workspace - cat < workspace.yaml - cpu: "2" - memory: "4" - home_disk_size: "2" - EOF - - coder create --template="kubernetes" test --rich-parameter-file ./workspace.yaml -y - coder stop test -y + coder create --template="kubernetes" kube --parameter cpu=2 --parameter memory=4 --parameter home_disk_size=2 -y + coder stop kube -y - name: Send Slack notification - if: needs.get_info.outputs.NEW == 'true' + if: needs.get_info.outputs.NEW == 'true' || github.event.inputs.deploy == 'true' run: | curl -s -o /dev/null -X POST -H 'Content-type: application/json' \ -d \ @@ -458,7 +435,7 @@ jobs: "pr_number": "'"${{ env.PR_NUMBER }}"'", "pr_url": "'"${{ env.PR_URL }}"'", "pr_title": "'"${{ env.PR_TITLE }}"'", - "pr_access_url": "'"https://${{ env.PR_DEPLOYMENT_ACCESS_URL }}"'", + "pr_access_url": "'"https://${{ env.PR_HOSTNAME }}"'", "pr_username": "'"test"'", "pr_email": "'"pr${{ env.PR_NUMBER }}@coder.com"'", "pr_password": "'"${{ steps.setup_deployment.outputs.password }}"'", diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 611faf4840..291f4e1444 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -84,11 +84,12 @@ You can test your changes by creating a PR deployment. There are two ways to do 1. By running `./scripts/deploy-pr.sh` 2. By manually triggering the [`pr-deploy.yaml`](https://github.com/coder/coder/actions/workflows/pr-deploy.yaml) GitHub Action workflow - ![Deploy PR manually](./images/pr-deploy-manual.png) + ![Deploy PR manually](./images/deploy-pr-manually.png) #### Available options -- `-s` or `--skip-build`, force prevents the build of the Docker image.(generally not needed as we are intelligently checking if the image needs to be built) +- `-d` or `--deploy`, force deploys the PR by deleting the existing deployment. +- `-b` or `--build`, force builds the Docker image. (generally not needed as we are intelligently checking if the image needs to be built) - `-e EXPERIMENT1,EXPERIMENT2` or `--experiments EXPERIMENT1,EXPERIMENT2`, will enable the specified experiments. (defaults to `*`) - `-n` or `--dry-run` will display the context without deployment. e.g., branch name and PR number, etc. - `-y` or `--yes`, will skip the CLI confirmation prompt. diff --git a/docs/images/deploy-pr-manually.png b/docs/images/deploy-pr-manually.png new file mode 100644 index 0000000000000000000000000000000000000000..718d00c65d1cced5f5defe15e4d07aefc4b6f16c GIT binary patch literal 13583 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 literal 0 HcmV?d00001 diff --git a/docs/images/pr-deploy-manual.png b/docs/images/pr-deploy-manual.png deleted file mode 100644 index eab92cc2249e75e08119bc1d1e4897545f6f75e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27155 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] [--build -b] [--deploy -d] # deploys the current branch to a PR environment and posts login credentials to # [#pr-deployments](https://codercom.slack.com/archives/C05DNE982E8) Slack channel set -euo pipefail # default settings -skipBuild=false dryRun=false confirm=true +build=false +deploy=false experiments="" # parse arguments while (("$#")); do case "$1" in - -s | --skip-build) - skipBuild=true + -b | --build) + build=true + shift + ;; + -d | --deploy) + deploy=true shift ;; -n | --dry-run) @@ -63,30 +68,20 @@ fi branchName=$(gh pr view --json headRefName | jq -r .headRefName) prNumber=$(gh pr view --json number | jq -r .number) -if $skipBuild; then - #check if the image exists - foundTag=$(curl -fsSL https://github.com/coder/coder/pkgs/container/coder-preview | grep -o "$prNumber" | head -n 1) || true - echo "foundTag is: '${foundTag}'" - if [[ -z "${foundTag}" ]]; then - echo "Image not found" - echo "${prNumber} tag not found in ghcr.io/coder/coder-preview" - echo "Please remove --skip-build and try again" - exit 1 - fi -fi - -if $dryRun; then +if [[ "$dryRun" = true ]]; then echo "dry run" echo "branchName: ${branchName}" echo "prNumber: ${prNumber}" - echo "skipBuild: ${skipBuild}" echo "experiments: ${experiments}" + echo "build: ${build}" + echo "deploy: ${deploy}" exit 0 fi echo "branchName: ${branchName}" echo "prNumber: ${prNumber}" -echo "skipBuild: ${skipBuild}" echo "experiments: ${experiments}" +echo "build: ${build}" +echo "deploy: ${deploy}" -gh workflow run pr-deploy.yaml --ref "${branchName}" -f "pr_number=${prNumber}" -f "skip_build=${skipBuild}" -f "experiments=${experiments}" +gh workflow run pr-deploy.yaml --ref "${branchName}" -f "pr_number=${prNumber}" -f "experiments=${experiments}" -f "build=${build}" -f "deploy=${deploy}"