From 0a7835b635da773e2f1ddad40399089f0c2b720c Mon Sep 17 00:00:00 2001 From: Luca Cavallaro Date: Wed, 31 Jul 2024 16:19:15 +0200 Subject: [PATCH] Add web_app_deploy workflow (#47) --- .github/workflows/web_app_deploy.yaml | 170 ++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 .github/workflows/web_app_deploy.yaml diff --git a/.github/workflows/web_app_deploy.yaml b/.github/workflows/web_app_deploy.yaml new file mode 100644 index 000000000..44708a969 --- /dev/null +++ b/.github/workflows/web_app_deploy.yaml @@ -0,0 +1,170 @@ +on: + workflow_call: + inputs: + workspace_name: + description: The name of the workspace to create the artifact for. + type: string + required: true + environment: + description: Environment where the artifact will be deployed. + type: string + required: true + resource_group_name: + description: Web App resource group name. + type: string + required: true + web_app_name: + description: Web App name. + type: string + required: true + use_staging_slot: + description: True if artifact should be deployed to staging slot + type: boolean + required: false + default: true + use_private_agent: + description: Use a private agent to deploy the built artifact. + type: boolean + required: false + default: true + +concurrency: + group: ${{ github.workflow }}-cd + cancel-in-progress: true + +env: + BUNDLE_NAME: bundle + +jobs: + build: + name: Build Artifact + runs-on: ubuntu-22.04 + env: + WORKSPACE: ${{ inputs.workspace_name }} + + steps: + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + name: Checkout + + - name: Prune + run: npx turbo@1.13.3 prune --scope ${{ env.WORKSPACE }} + + - name: Setup Node.js + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + with: + node-version-file: ".node-version" + cache: "yarn" + cache-dependency-path: "./out/yarn.lock" + + - name: Install dependencies + run: yarn install --immutable + working-directory: ./out + + - name: Build + run: yarn build + working-directory: ./out + + - name: Build the artifact + id: make-artifact + env: + # we fall back to node-moules, even in case pnp is configured, in order to avoid bundling dependendencies + YARN_NODE_LINKER: node-modules + YARN_NM_HOISTING_LIMITS: workspaces + run: | + set -e + + # determine the entry point file name from package.json main property + ENTRY_POINT=$(jq -r '.main' package.json) + + if [ -z $ENTRY_POINT ]; then + echo "::error::invalid target" + exit 1 + fi + + # generate node_modules folder excluding devDependencies + yarn workspaces focus --production + + FORMAT=$(jq -r 'if .type == "module" then "esm" else "cjs" end' package.json) + SHORT_SHA=$(git rev-parse --short ${{ github.sha }}) + + mkdir ${{ github.run_id }} + + # bundle compiled code, excluding node_modules + curl -fsSL https://esbuild.github.io/dl/v0.21.5 | sh + ./esbuild index=$ENTRY_POINT --bundle --format=$FORMAT --platform=node --target=node20 --packages=external --outdir=${{ github.run_id }} --allow-overwrite + + # create a new package.json file, with the updated entry point + jq --arg sha "+$SHORT_SHA" '{"name": .name, "version": (.version + $sha), "main": "index.js", "type": (if has("type") then .type else "commonjs" end), "dependencies": .dependencies}' package.json > ${{ github.run_id }}/package.json + + DEFAULT_FILES='"host.json"' + FILES=$(jq -r --arg default "$DEFAULT_FILES" 'if has("files") then .files else [$default] end | join(" ")' package.json) + + # create the artifact (zip) with node_modules, index.js and package.json + zip -r $BUNDLE_NAME.zip node_modules $FILES + zip -ju $BUNDLE_NAME.zip ${{ github.run_id }}/index.js ${{ github.run_id }}/package.json + + echo "artifact-path=$(realpath $BUNDLE_NAME.zip)" >> "$GITHUB_OUTPUT" + working-directory: ./out/apps/${{ inputs.workspace_name }} + + - name: Upload Artifact + uses: actions/upload-artifact@694cdabd8bdb0f10b2cea11669e1bf5453eed0a6 # v4.2.0 + with: + name: ${{ env.BUNDLE_NAME }} + path: ${{ steps.make-artifact.outputs.artifact-path }} + if-no-files-found: error + retention-days: 7 + + deploy: + name: Deploy + if: ${{ !github.event.act }} + needs: [build] + runs-on: ${{ inputs.use_private_agent == true && 'self-hosted' || 'ubuntu-22-04' }} + environment: ${{ inputs.environment }}-cd + permissions: + id-token: write + contents: read + + steps: + - name: Download Artifact + uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1 + with: + name: ${{ env.BUNDLE_NAME }} + + - name: Azure Login + uses: azure/login@v2 # v2.0.0 + env: + ARM_USE_OIDC: true + with: + client-id: ${{ secrets.ARM_CLIENT_ID }} + tenant-id: ${{ secrets.ARM_TENANT_ID }} + subscription-id: ${{ secrets.ARM_SUBSCRIPTION_ID }} + + - name: Deploy + if: ${{ inputs.use_staging_slot == false }} + run: | + az webapp deploy \ + --resource-group ${{ inputs.resource_group_name }} \ + --name ${{ inputs.web_app_name }} \ + --src-path ${{ env.BUNDLE_NAME }}.zip \ + --type zip \ + --async false + + - name: Deploy to Staging Slot + if: ${{ inputs.use_staging_slot == true }} + run: | + az webapp deploy \ + --resource-group ${{ inputs.resource_group_name }} \ + --name ${{ inputs.web_app_name }} \ + --slot staging \ + --src-path ${{ env.BUNDLE_NAME }}.zip \ + --type zip \ + --async false + + - name: Swap Staging and Production Slots + if: ${{ inputs.use_staging_slot == true }} + run: | + az webapp deployment slot swap \ + -g ${{ inputs.resource_group_name }} \ + -n ${{ inputs.web_app_name }} \ + --slot staging \ + --target-slot production