diff --git a/.github/workflows/deploy-pr-preview.yml b/.github/workflows/deploy-pr-preview.yml index dc80619c4..a5da9a32e 100644 --- a/.github/workflows/deploy-pr-preview.yml +++ b/.github/workflows/deploy-pr-preview.yml @@ -9,16 +9,34 @@ on: paths: - "docs/sources/**" +concurrency: + group: ${{ github.workflow }}-${{ inputs.repo }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: deploy-pr-preview: - if: ! github.event.pull_request.head.repo.fork - uses: grafana/writers-toolkit/.github/workflows/deploy-preview.yml@main - with: - sha: ${{ github.event.pull_request.head.sha }} - branch: ${{ github.head_ref }} - event_number: ${{ github.event.number }} - title: ${{ github.event.pull_request.title }} - repo: writers-toolkit - website_directory: content/docs/writers-toolkit - relative_prefix: /docs/writers-toolkit/ - index_file: false + if: ${{ ! github.event.pull_request.head.repo.fork }} + permissions: + contents: read + id-token: write # Fetch Vault secrets. + pull-requests: write # Create or update PR comments. + statuses: write # Update GitHub status check with deploy preview link. + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + + - uses: ./deploy-preview + with: + branch: ${{ github.head_ref }} + event_number: ${{ github.event.number }} + sha: ${{ github.event.pull_request.head.sha }} + sources: | + [{ + "index_file": null, + "relative_prefix": "/docs/writers-toolkit/", + "source_directory": "docs/sources", + "website_directory": "content/docs/writers-toolkit" + }] + title: ${{ github.event.pull_request.title }} diff --git a/.github/workflows/deploy-preview.yml b/.github/workflows/deploy-preview.yml index 76af7a790..3ddc9f79e 100644 --- a/.github/workflows/deploy-preview.yml +++ b/.github/workflows/deploy-preview.yml @@ -3,6 +3,54 @@ name: deploy-preview on: workflow_call: inputs: + sources: + description: | + sources is a JSON string describing multiple source mounts. + + It replaces the individual inputs used in the previous version of this action. + + The structure is an array of objects, each with the following keys: + + index_file: + description: Path to index file used to redirect versioned projects. For example, "content/docs/mimir/_index.md". + required: true + type: string + relative_prefix: + description: The URL path prefix for the mount destination. For example, "/docs/mimir/latest/". + required: true + type: string + repo: + description: The Grafana repository name for the source files. For example, "mimir". + required: true + type: string + source_directory: + description: The path to the source files in the repository. For example, "docs/sources". + required: true + type: string + website_directory: + description: The path to mount the documentation in the website content structure. For example, "content/docs/mimir/latest". + required: true + type: string + + The following example mounts both the Mimir documentation and the mimir-distributed Helm chart documentation: + + [ + { + "index_file": true, + "relative_prefix": "/docs/mimir/latest/", + "repo": "mimir", + "source_directory": "docs/sources/mimir", + "website_directory": "content/docs/mimir/latest" + }, + { + "index_file": true, + "relative_prefix": "/docs/helm-charts/mimir-distributed/latest/", + "repo": "mimir", + "source_directory": "docs/sources/mimir-distributed", + "website_directory": "content/docs/helm-charts/mimir-distributed/latest" + } + ] + type: string sha: required: true type: string @@ -15,21 +63,6 @@ on: title: required: true type: string - repo: - required: true - type: string - source_directory: - default: docs/sources - type: string - website_directory: - required: true - type: string - relative_prefix: - required: true - type: string - index_file: # creates the necessary project _index.md file for versioned repos - required: true - type: boolean env: CLOUD_RUN_REGION: us-south1 @@ -67,19 +100,12 @@ jobs: body: | :building_construction: Updating deploy preview... - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - if: github.event.action == 'opened' || github.event.action == 'synchronize' - with: - sparse-checkout-cone-mode: false # exclude root files - sparse-checkout: docs - - # get the Dockerfile and nginx conf - name: Sparse checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 if: github.event.action == 'opened' || github.event.action == 'synchronize' with: repository: "grafana/writers-toolkit" - ref: "main" + ref: jdb/2025-01-multiple-source-deploy-preview path: deploy-preview-files sparse-checkout: | deploy-preview diff --git a/deploy-preview/Dockerfile b/deploy-preview/Dockerfile index 354785ef1..ebe234efa 100644 --- a/deploy-preview/Dockerfile +++ b/deploy-preview/Dockerfile @@ -1,11 +1,13 @@ FROM nginx:1.21.6-alpine - -COPY ./dist /usr/share/nginx/dist +ARG ACTION_PATH +ENV ACTION_PATH="${ACTION_PATH:-deploy-preview-files/deploy-preview/}" RUN rm -rf /etc/nginx/sites-enabled && \ rm -rf /etc/nginx/nginx.conf -COPY deploy-preview-files/deploy-preview/nginx.conf /etc/nginx/nginx.conf +COPY "${ACTION_PATH}/deploy-preview-files/deploy-preview/nginx.conf" /etc/nginx/nginx.conf COPY build.conf /etc/nginx/build.conf -RUN nginx -t \ No newline at end of file +COPY ./dist /usr/share/nginx/dist + +RUN nginx -t diff --git a/deploy-preview/action.yml b/deploy-preview/action.yml new file mode 100644 index 000000000..44be6712c --- /dev/null +++ b/deploy-preview/action.yml @@ -0,0 +1,218 @@ +name: deploy-preview + +inputs: + sha: + required: true + type: string + branch: + required: true + type: string + event_number: + required: true + type: string + title: + required: true + type: string + + sources: + description: | + sources is a JSON string describing multiple source mounts. + It replaces the individual inputs used in the previous version of this action. + The structure is an array of objects, each with the following keys: + index_file: + description: Path to index file used to redirect versioned projects. For example, "content/docs/mimir/_index.md". + required: true + type: string + relative_prefix: + description: The URL path prefix for the mount destination. For example, "/docs/mimir/latest/". + required: true + type: string + repo: + description: The Grafana repository name for the source files. For example, "mimir". + required: true + type: string + source_directory: + description: The path to the source files in the repository. For example, "docs/sources". + required: true + type: string + website_directory: + description: The path to mount the documentation in the website content structure. For example, "content/docs/mimir/latest". + required: true + type: string + The following example mounts both the Mimir documentation and the mimir-distributed Helm chart documentation: + [ + { + "index_file": true, + "relative_prefix": "/docs/mimir/latest/", + "repo": "mimir", + "source_directory": "docs/sources/mimir", + "website_directory": "content/docs/mimir/latest" + }, + { + "index_file": true, + "relative_prefix": "/docs/helm-charts/mimir-distributed/latest/", + "repo": "mimir", + "source_directory": "docs/sources/mimir-distributed", + "website_directory": "content/docs/helm-charts/mimir-distributed/latest" + } + ] + type: string + + +runs: + using: composite + steps: + - name: Find comment + uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0 + id: fc + with: + issue-number: ${{ inputs.event_number }} + comment-author: "github-actions[bot]" + body-includes: Deploy preview available + + - name: Update comment with in-progress deploy preview + if: steps.fc.outputs.comment-id != '' && (github.event.action == 'opened' || github.event.action == 'synchronize') + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 + with: + comment-id: ${{ steps.fc.outputs.comment-id }} + issue-number: ${{ inputs.event_number }} + edit-mode: append + append-separator: newline + body: | + :building_construction: Updating deploy preview... + + - name: Build website + if: github.event.action == 'opened' || github.event.action == 'synchronize' + env: + ACTION_PATH: ${{ github.action_path }} + SOURCES: ${{ inputs.sources }} + run: | + "${ACTION_PATH}/build.sh" + shell: bash + + - name: Print build header value + if: github.event.action == 'opened' || github.event.action == 'synchronize' + env: + SHA: ${{ inputs.sha }} + run: | + printf "%s" "add_header 'Build' '"${SHA}"';" > build.conf + shell: bash + + # - uses: google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f # v2.1.7 + # if: github.event.action == 'opened' || github.event.action == 'synchronize' + # id: gcloud-auth + # with: + # token_format: access_token + # workload_identity_provider: "projects/304398677251/locations/global/workloadIdentityPools/github/providers/github-provider" + # service_account: github-docs-deploy-previews@grafanalabs-workload-identity.iam.gserviceaccount.com + # create_credentials_file: false + + # - name: Login to GAR + # if: github.event.action == 'opened' || github.event.action == 'synchronize' + # uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + # with: + # registry: us-docker.pkg.dev + # username: oauth2accesstoken + # password: ${{ steps.gcloud-auth.outputs.access_token }} + + - name: Extract metadata (tags, labels) for Docker + if: github.event.action == 'opened' || github.event.action == 'synchronize' + id: meta + uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1 + with: + images: "us-docker.pkg.dev/grafanalabs-dev/docker-docs-previews-dev/${{ inputs.repo }}" + tags: |- + "${{ inputs.sha }}" + "${{ inputs.event_number }}" + + - name: Set up Docker Buildx + if: github.event.action == 'opened' || github.event.action == 'synchronize' + uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + with: + cache-binary: false + driver: docker-container + + - name: Build the container + if: github.event.action == 'opened' || github.event.action == 'synchronize' + uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0 + id: build + with: + build-args: | + ACTION_PATH=${{ github.action_path }} + file: ${{ github.action_path }}/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + + - uses: google-github-actions/auth@8254fb75a33b976a221574d287e93919e6a36f70 # v2.1.6 + id: gcloud-auth-cloud-run + with: + workload_identity_provider: "projects/304398677251/locations/global/workloadIdentityPools/github/providers/github-provider" + service_account: "github-docs-cloud-run-dev@grafanalabs-workload-identity.iam.gserviceaccount.com" + + - name: Deploy to Cloud Run + uses: google-github-actions/deploy-cloudrun@1ec29da1351112c7904fb60454a55e3e1021a51c # v2.7.2 + if: github.event.action == 'opened' || github.event.action == 'synchronize' + id: deploy + with: + image: us-docker.pkg.dev/grafanalabs-dev/docker-docs-previews-dev/${{ inputs.repo }}:${{ inputs.sha }} + service: deploy-preview-${{ inputs.repo }}-${{ inputs.event_number }} + project_id: grafanalabs-dev + region: us-south1 + flags: --port=80 --ingress=all --allow-unauthenticated + + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@6189d56e4096ee891640bb02ac264be376592d6a # v2.1.2 + if: github.event.action == 'closed' + with: + project_id: grafanalabs-dev + + - name: Delete deploy preview + if: github.event.action == 'closed' + env: + REPO: ${{ inputs.repo }} + EVENT_NUMBER: ${{ inputs.event_number }} + run: | + SERVICE_NAME="deploy-preview-${REPO}-${EVENT_NUMBER}" + if gcloud run services describe $SERVICE_NAME --region=${{ env.CLOUD_RUN_REGION }} --project=grafanalabs-dev > /dev/null 2>&1; then + gcloud run services delete $SERVICE_NAME --region=${{ env.CLOUD_RUN_REGION }} --project=grafanalabs-dev --quiet + else + echo "Service $SERVICE_NAME does not exist. Skipping step." + fi + shell: bash + + - name: Send commit status + uses: ouzi-dev/commit-status-updater@26588d166ff273fc4c0664517359948f7cdc9bf1 # v2.0.2 + if: github.event.action == 'opened' || github.event.action == 'synchronize' + with: + name: deploy_preview + status: "${{ job.status }}" + url: "${{ steps.deploy.outputs.url }}${{ inputs.relative_prefix }}" + description: "Public deploy preview" + + - name: Create comment with available deploy preview + if: (github.event.action == 'opened' || github.event.action == 'synchronize') && steps.fc.outputs.comment-id == '' + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 + with: + issue-number: ${{ inputs.event_number }} + body: | + :computer: Deploy preview available: ${{ steps.deploy.outputs.url }}${{ inputs.relative_prefix }} + + - name: Update comment with deleted deploy preview + if: github.event.action == 'closed' + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 + with: + issue-number: ${{ inputs.event_number }} + comment-id: ${{ steps.fc.outputs.comment-id }} + edit-mode: replace + body: | + :computer: Deploy preview deleted. + + - name: Update comment with available deploy preview + if: (github.event.action == 'opened' || github.event.action == 'synchronize') && steps.fc.outputs.comment-id != '' + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 + with: + comment-id: ${{ steps.fc.outputs.comment-id }} + issue-number: ${{ inputs.event_number }} + edit-mode: replace + body: | + :computer: Deploy preview available: ${{ steps.deploy.outputs.url }}${{ inputs.relative_prefix }} diff --git a/deploy-preview/build.sh b/deploy-preview/build.sh index c1e53e3ca..3adeed991 100755 --- a/deploy-preview/build.sh +++ b/deploy-preview/build.sh @@ -1,24 +1,77 @@ #!/usr/bin/env bash -docker run \ - -v "${PWD}/dist:/hugo/dist" \ - -v "${PWD}/${SOURCE_DIRECTORY}:/hugo/${WEBSITE_DIRECTORY}" \ - -e INDEX_FILE \ - -e REPO \ - -e WEBSITE_DIRECTORY \ - --rm grafana/docs-base:latest \ - /bin/bash \ - -c ' -if [[ "${INDEX_FILE}" == "true" ]]; then - echo "Creating custom _index.md" && \ - cat > "/hugo/content/docs/${REPO}/_index.md" </dev/null; then + echo "SOURCES environment variable is not valid JSON" +fi + +tempfile="$(mktemp -t deploy-preview.XXX)" +# Pull out the relevant fields from the JSON because the grafana/docs-base image doesn't have jq. +index_files=() +while read -r index_file relative_prefix; do + if [[ "${index_file}" != 'null' ]]; then + index_files+=("${index_file}:${relative_prefix}") + fi +done < <(jq -r '.[] | "\(.index_file) \(.relative_prefix)"' <<<"${SOURCES}") +cat <"${tempfile}" +#!/usr/bin/env bash + +# Create an index file to redirect a project root to the correct versioned URL. +for pair in ${index_files[@]}; do + IFS=':' read -r index_file relative_prefix <<<"\${pair}" + dst="/hugo/\${index_file}" + parent="\${dst%/*}" + echo "Creating custom index: \${dst} -> \${relative_prefix}" + + title="\${dst%/*}" + while [[ -n "\${parent}" ]]; do + if [[ ! -f "\${parent}/_index.md" ]]; then + cat > "\${parent}/_index.md" <}} +EOPARENT + fi + parent="\${parent%/*}" + done + + cat > "\${dst}" <