name: Preview (deploy) on: workflow_run: workflows: - 'Preview (build)' types: - completed jobs: cache-manifests-file: name: Cache Manifests File runs-on: ubuntu-latest if: ${{ github.event.workflow_run.conclusion == 'success' }} permissions: # "If you specify the access for any of these scopes, all of those that are not specified are set to none." # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions actions: read # Access cache outputs: manifests-cache-key: ${{ steps.hash.outputs.MANIFESTS_FILE_HASH }} git-ref: ${{ steps.event.outputs.GIT_REF }} pr-number: ${{ steps.event.outputs.PR_NUMBER }} action: ${{ steps.event.outputs.ACTION }} steps: - name: Harden Runner uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 with: disable-sudo: true egress-policy: block allowed-endpoints: > api.github.com:443 - name: 'Download artifacts' # Fetch output (zip archive) from the workflow run that triggered this workflow. uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 with: script: | let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ owner: context.repo.owner, repo: context.repo.repo, run_id: context.payload.workflow_run.id, }); let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => { return artifact.name == "preview-spec" })[0]; if (matchArtifact === undefined) { throw TypeError('Build Artifact not found!'); } let download = await github.rest.actions.downloadArtifact({ owner: context.repo.owner, repo: context.repo.repo, artifact_id: matchArtifact.id, archive_format: 'zip', }); let fs = require('fs'); fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/preview-spec.zip`, Buffer.from(download.data)); - name: 'Accept event from first stage' run: unzip preview-spec.zip event.json - name: Read Event into ENV id: event run: | echo PR_NUMBER=$(jq '.number | tonumber' < event.json) >> $GITHUB_OUTPUT echo ACTION=$(jq --raw-output '.action | tostring | [scan("\\w+")][0]' < event.json) >> $GITHUB_OUTPUT echo GIT_REF=$(jq --raw-output '.pull_request.head.sha | tostring | [scan("\\w+")][0]' < event.json) >> $GITHUB_OUTPUT - name: Hash Rendered Manifests File id: hash # If the previous workflow was triggered by a PR close event, we will not have a manifests file artifact. if: ${{ steps.event.outputs.ACTION != 'closed' }} run: | unzip preview-spec.zip manifests.rendered.yml ls echo "MANIFESTS_FILE_HASH=$(md5sum manifests.rendered.yml | awk '{ print $1 }')" >> $GITHUB_OUTPUT - name: Cache Manifests File if: ${{ steps.event.outputs.ACTION != 'closed' }} uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 with: path: manifests.rendered.yml key: ${{ steps.hash.outputs.MANIFESTS_FILE_HASH }} - name: DEBUG - Print Job Outputs if: ${{ runner.debug }} run: | echo "PR number: ${{ steps.event.outputs.PR_NUMBER }}" echo "Git Ref: ${{ steps.event.outputs.GIT_REF }}" echo "Action: ${{ steps.event.outputs.ACTION }}" echo "Manifests file hash: ${{ steps.hash.outputs.MANIFESTS_FILE_HASH }}" cat event.json deploy-uffizzi-preview: name: Deploy to Uffizzi Virtual Cluster needs: - cache-manifests-file if: ${{ github.event.workflow_run.conclusion == 'success' && needs.cache-manifests-file.outputs.action != 'closed' }} permissions: contents: read pull-requests: write id-token: write runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 # Identify comment to be updated - name: Find comment for Ephemeral Environment uses: peter-evans/find-comment@d5fe37641ad8451bdd80312415672ba26c86575e # v3 id: find-comment with: issue-number: ${{ needs.cache-manifests-file.outputs.pr-number }} comment-author: 'github-actions[bot]' body-includes: pr-${{ needs.cache-manifests-file.outputs.pr-number }} direction: last # Create/Update comment with action deployment status - name: Create or Update Comment with Deployment Notification id: notification uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4 with: comment-id: ${{ steps.find-comment.outputs.comment-id }} issue-number: ${{ needs.cache-manifests-file.outputs.pr-number }} body: | ## Uffizzi Ephemeral Environment - Virtual Cluster :cloud: deploying cluster `pr-${{ needs.cache-manifests-file.outputs.pr-number }}` :gear: Updating now by workflow run [${{ github.run_id }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}). Download the Uffizzi CLI to interact with the upcoming virtual cluster https://docs.uffizzi.com/install edit-mode: replace - name: Connect to Virtual Cluster uses: UffizziCloud/cluster-action@main with: cluster-name: pr-${{ needs.cache-manifests-file.outputs.pr-number }} server: https://app.uffizzi.com - name: Fetch cached Manifests File id: cache uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4 with: path: manifests.rendered.yml key: ${{ needs.cache-manifests-file.outputs.manifests-cache-key }} - name: Kustomize and Apply Manifests id: prev run: | # Apply kustomized manifests to virtual cluster. export KUBECONFIG=`pwd`/kubeconfig kubectl apply -f manifests.rendered.yml --kubeconfig ./kubeconfig # Allow uffizzi to sync the resources sleep 10 # Get the hostnames assigned by uffizzi export BACKSTAGE_HOST=$(kubectl get ingress backstage --kubeconfig kubeconfig -o json | jq '.spec.rules[0].host' | tr -d '"') export UFFIZZI_CLUSTER_APISERVER=$(kubectl config view --minify | grep server | cut -f 2- -d ":" | tr -d " ") # Patch backstage deployment to use UFFIZZI_URL kubectl patch deployment backstage --kubeconfig kubeconfig -p '{"spec": {"template": {"spec": {"containers": [{"name": "backstage", "args":["-c", "APP_CONFIG_app_baseUrl='https://${BACKSTAGE_HOST}' APP_CONFIG_backend_baseUrl='https://${BACKSTAGE_HOST}' APP_CONFIG_auth_environment='production' node packages/backend --config app-config.yaml"], "env": [{"name": "UFFIZZI_URL", "value": "'https://${BACKSTAGE_HOST}'"}, {"name": "UFFIZZI_CLUSTER_APISERVER", "value": "'${UFFIZZI_CLUSTER_APISERVER}'"}, {"name": "GITHUB_SHA", "value": "'${GITHUB_SHA}'"}, {"name": "REF_NAME", "value": "'${{ needs.cache-manifests-file.outputs.git-ref }}'"}]}]}}}}' if [[ ${RUNNER_DEBUG} == 1 ]]; then kubectl get all --kubeconfig ./kubeconfig fi echo "backstage_url=${BACKSTAGE_HOST}" >> $GITHUB_OUTPUT echo "Access the \`backstage\` endpoint at [\`${BACKSTAGE_HOST}\`](http://${BACKSTAGE_HOST})" >> $GITHUB_STEP_SUMMARY - name: Create or Update Comment with Deployment URL uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4 with: comment-id: ${{ steps.notification.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} body: | ## Uffizzi Ephemeral Environment - Virtual Cluster Your cluster `pr-${{ needs.cache-manifests-file.outputs.pr-number }}` was successfully created. Learn more about [Uffizzi virtual clusters](https://docs.uffizzi.com/topics/virtual-clusters) To connect to this cluster, follow these steps: 1. Download and install the Uffizzi CLI from https://docs.uffizzi.com/install 2. Login to Uffizzi, then select the `backstage` account and project: ``` uffizzi login ``` ``` Select an account: ‣ ${{ github.event.repository.name }} jdoe Select a project or create a new project: ‣ ${{ github.event.repository.name }}-6783521 ``` 3. Update your kubeconfig: `uffizzi cluster update-kubeconfig pr-${{ needs.cache-manifests-file.outputs.pr-number }} --kubeconfig=[PATH_TO_KUBECONFIG]` After updating your kubeconfig, you can manage your cluster with `kubectl`, `kustomize`, `helm`, and other tools that use kubeconfig files: `kubectl get namespace --kubeconfig [PATH_TO_KUBECONFIG]` Access the `backstage` endpoint at [`https://${{ steps.prev.outputs.backstage_url }}`](https://${{ steps.prev.outputs.backstage_url }}) edit-mode: replace delete-uffizzi-preview: permissions: contents: read pull-requests: write id-token: write name: Delete the Uffizzi Virtual Cluster needs: - cache-manifests-file if: ${{ needs.cache-manifests-file.outputs.action == 'closed' }} runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 - name: Delete Virtual Cluster uses: UffizziCloud/cluster-action@main with: cluster-name: pr-${{ needs.cache-manifests-file.outputs.pr-number }} server: https://app.uffizzi.com action: delete # Identify comment to be updated - name: Find comment for Ephemeral Environment uses: peter-evans/find-comment@d5fe37641ad8451bdd80312415672ba26c86575e # v3 id: find-comment with: issue-number: ${{ needs.cache-manifests-file.outputs.pr-number }} comment-author: 'github-actions[bot]' body-includes: pr-${{ needs.cache-manifests-file.outputs.pr-number }} direction: last - name: Update Comment with Deletion uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4 with: comment-id: ${{ steps.find-comment.outputs.comment-id }} issue-number: ${{ needs.cache-manifests-file.outputs.pr-number }} body: | Uffizzi Cluster `pr-${{ needs.cache-manifests-file.outputs.pr-number }}` was deleted. edit-mode: replace