Security: remove Docker PR build/deploy workflows (#21599)
* Remove Docker PR build/deploy workflows * Remove supporting Docker deploy script
This commit is contained in:
92
.github/workflows/staging-build-pr-docker.yml
vendored
92
.github/workflows/staging-build-pr-docker.yml
vendored
@@ -1,92 +0,0 @@
|
||||
name: Staging - Build PR Docker
|
||||
|
||||
# **What it does**: Builds PRs before deploying them.
|
||||
# **Why we have it**: Because it's not safe to share our deploy secrets with forked repos: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
|
||||
# **Who does it impact**: All contributors.
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- synchronize
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
if: >-
|
||||
${{
|
||||
(github.repository == 'github/docs-internal' || github.repository == 'github/docs') &&
|
||||
startsWith(github.head_ref, 'docker-')
|
||||
}}
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
concurrency:
|
||||
group: staging_docker_${{ github.head_ref }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||
|
||||
# Make sure only approved files are changed if it's in github/docs
|
||||
- name: Check changed files
|
||||
if: ${{ github.repository == 'github/docs' && github.event.pull_request.user.login != 'Octomerger' }}
|
||||
uses: dorny/paths-filter@eb75a1edc117d3756a18ef89958ee59f9500ba58
|
||||
id: filter
|
||||
with:
|
||||
# Base branch used to get changed files
|
||||
base: 'main'
|
||||
|
||||
# Enables setting an output in the format in `${FILTER_NAME}_files
|
||||
# with the names of the matching files formatted as JSON array
|
||||
list-files: json
|
||||
|
||||
# Returns list of changed files matching each filter
|
||||
filters: |
|
||||
notAllowed:
|
||||
- '*.js'
|
||||
- '*.mjs'
|
||||
- '*.ts'
|
||||
- '*.tsx'
|
||||
- '*.json'
|
||||
- '.npmrc'
|
||||
- 'script/**'
|
||||
- 'Dockerfile*'
|
||||
|
||||
# When there are changes to files we can't accept
|
||||
- name: Fail when disallowed files are changed
|
||||
if: ${{ steps.filter.outputs.notAllowed == 'true' }}
|
||||
run: exit 1
|
||||
|
||||
- name: Create an archive
|
||||
run: |
|
||||
tar -c --file=app.tar \
|
||||
assets/ \
|
||||
content/ \
|
||||
stylesheets/ \
|
||||
pages/ \
|
||||
data/ \
|
||||
includes/ \
|
||||
lib/ \
|
||||
middleware/ \
|
||||
translations/ \
|
||||
server.mjs \
|
||||
package*.json \
|
||||
.npmrc \
|
||||
feature-flags.json \
|
||||
next.config.js \
|
||||
tsconfig.json \
|
||||
next-env.d.ts \
|
||||
Dockerfile
|
||||
|
||||
# Upload only the files needed to run + build this application.
|
||||
# We are not willing to trust the rest (e.g. script/) for the remainder
|
||||
# of the deployment process.
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@27121b0bdffd731efa15d66772be8dc71245d074
|
||||
with:
|
||||
name: pr_build_docker
|
||||
path: app.tar
|
||||
342
.github/workflows/staging-deploy-pr-docker.yml
vendored
342
.github/workflows/staging-deploy-pr-docker.yml
vendored
@@ -1,342 +0,0 @@
|
||||
name: Staging - Deploy PR Docker
|
||||
|
||||
# **What it does**: To deploy PRs to a Heroku staging environment.
|
||||
# **Why we have it**: To deploy with high visibility in case of failures.
|
||||
# **Who does it impact**: All contributors.
|
||||
|
||||
on:
|
||||
workflow_run:
|
||||
workflows:
|
||||
- 'Staging - Build PR Docker'
|
||||
types:
|
||||
- completed
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
deployments: write
|
||||
pull-requests: read
|
||||
statuses: write
|
||||
|
||||
env:
|
||||
EARLY_ACCESS_SCRIPT_PATH: script/early-access/clone-for-build.js
|
||||
EARLY_ACCESS_SUPPORT_FILES: script/package.json
|
||||
# In this specific workflow relationship, the `github.event.workflow_run.pull_requests`
|
||||
# array will always contain only 1 item! Specifically, it will contain the PR associated
|
||||
# with the `github.event.workflow_run.head_branch` that triggered the preceding
|
||||
# `pull_request` event that triggered the "Staging - Build PR" workflow.
|
||||
PR_URL: ${{ github.event.workflow_run.repository.html_url }}/pull/${{ github.event.workflow_run.pull_requests[0].number }}
|
||||
BUILD_ACTIONS_RUN_LOG: https://github.com/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}
|
||||
|
||||
jobs:
|
||||
notify-of-failed-builds:
|
||||
if: >-
|
||||
${{
|
||||
github.event.workflow_run.conclusion == 'failure' &&
|
||||
(github.repository == 'github/docs-internal' || github.repository == 'github/docs') &&
|
||||
startsWith(github.event.workflow_run.head_branch, 'docker-')
|
||||
}}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 1
|
||||
# Specifically omitting a concurrency group here in case the build was not
|
||||
# successful BECAUSE a subsequent build already canceled it
|
||||
steps:
|
||||
- name: Send Slack notification if build workflow failed
|
||||
uses: someimportantcompany/github-actions-slack-message@0b470c14b39da4260ed9e3f9a4f1298a74ccdefd
|
||||
with:
|
||||
channel: ${{ secrets.DOCS_STAGING_DEPLOYMENT_FAILURES_SLACK_CHANNEL_ID }}
|
||||
bot-token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
|
||||
color: failure
|
||||
text: Staging build (docker) failed for PR ${{ env.PR_URL }} at commit ${{ github.event.workflow_run.head_sha }}. See ${{ env.BUILD_ACTIONS_RUN_LOG }}
|
||||
|
||||
check-pr-before-prepare:
|
||||
if: >-
|
||||
${{
|
||||
github.event.workflow_run.conclusion == 'success' &&
|
||||
(github.repository == 'github/docs-internal' || github.repository == 'github/docs') &&
|
||||
startsWith(github.event.workflow_run.head_branch, 'docker-')
|
||||
}}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 1
|
||||
concurrency:
|
||||
group: staging_docker_${{ github.event.workflow_run.head_branch }}
|
||||
cancel-in-progress: true
|
||||
outputs:
|
||||
pull_request_state: ${{ steps.check-pr.outputs.state }}
|
||||
steps:
|
||||
- name: Check pull request state
|
||||
id: check-pr
|
||||
uses: actions/github-script@2b34a689ec86a68d8ab9478298f91d5401337b7d
|
||||
with:
|
||||
script: |
|
||||
const { owner, repo } = context.repo
|
||||
const { data: pullRequest } = await github.pulls.get({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: ${{ github.event.workflow_run.pull_requests[0].number }}
|
||||
})
|
||||
core.setOutput('state', pullRequest.state)
|
||||
|
||||
prepare:
|
||||
needs: check-pr-before-prepare
|
||||
if: ${{ needs.check-pr-before-prepare.outputs.pull_request_state == 'open' }}
|
||||
env:
|
||||
HEROKU_API_KEY: ${{ secrets.HEROKU_API_TOKEN }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 20
|
||||
concurrency:
|
||||
group: staging_docker_${{ github.event.workflow_run.head_branch }}
|
||||
cancel-in-progress: true
|
||||
outputs:
|
||||
source_blob_url: ${{ steps.build-source.outputs.download_url }}
|
||||
app_name: ${{ steps.create-app.outputs.app_name}}
|
||||
docker_image_id: ${{ steps.image-id.outputs.image_id}}
|
||||
steps:
|
||||
- name: Check out repo's default branch
|
||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||
with:
|
||||
# For enhanced security: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
|
||||
persist-credentials: 'false'
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@38d90ce44d5275ad62cc48384b3d8a58c500bb5f
|
||||
with:
|
||||
node-version: 16.8.x
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- if: ${{ github.repository == 'github/docs-internal' }}
|
||||
name: Clone early access
|
||||
run: node ${{ env.EARLY_ACCESS_SCRIPT_PATH }}
|
||||
env:
|
||||
DOCUBOT_REPO_PAT: ${{ secrets.DOCUBOT_REPO_PAT }}
|
||||
GIT_BRANCH: ${{ github.event.workflow_run.head_branch }}
|
||||
|
||||
- if: ${{ github.repository == 'github/docs-internal' }}
|
||||
name: Create an archive for early access
|
||||
run: |
|
||||
tar -c --file=early-access.tar \
|
||||
assets/ \
|
||||
content/ \
|
||||
data/
|
||||
|
||||
# Download the previously built "app.tar"
|
||||
- name: Download build artifact
|
||||
uses: dawidd6/action-download-artifact@b9571484721e8187f1fd08147b497129f8972c74
|
||||
with:
|
||||
workflow: ${{ github.event.workflow_run.workflow_id }}
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
name: pr_build
|
||||
path: ./
|
||||
|
||||
# Append "early-access.tar" into "app.tar"
|
||||
- if: ${{ github.repository == 'github/docs-internal' }}
|
||||
name: Concatenate the archives
|
||||
run: tar -A --file=app.tar early-access.tar
|
||||
|
||||
- name: Create app
|
||||
id: create-app
|
||||
uses: actions/github-script@2b34a689ec86a68d8ab9478298f91d5401337b7d
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
HEROKU_API_TOKEN: ${{ secrets.HEROKU_API_TOKEN }}
|
||||
HYDRO_ENDPOINT: ${{ secrets.HYDRO_ENDPOINT }}
|
||||
HYDRO_SECRET: ${{ secrets.HYDRO_SECRET }}
|
||||
with:
|
||||
script: |
|
||||
const esm = require('esm')
|
||||
require = esm({})
|
||||
|
||||
const { default: createApp } = require('./script/deployment/create-app.js')
|
||||
const { default: parsePrUrl } = require('./script/deployment/parse-pr-url.js')
|
||||
const { default: getOctokit } = require('./script/helpers/github')
|
||||
|
||||
// This helper uses the `GITHUB_TOKEN` implicitly!
|
||||
// We're using our usual version of Octokit vs. the provided `github`
|
||||
// instance to avoid versioning discrepancies.
|
||||
const octokit = getOctokit()
|
||||
|
||||
try {
|
||||
const { owner, repo, pullNumber } = parsePrUrl(process.env.PR_URL)
|
||||
if (!owner || !repo || !pullNumber) {
|
||||
throw new Error(`'pullRequestUrl' input must match URL format 'https://github.com/github/(docs|docs-internal)/pull/123' but was '${process.env.PR_URL}'`)
|
||||
}
|
||||
|
||||
const { data: pullRequest } = await octokit.pulls.get({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: pullNumber
|
||||
})
|
||||
|
||||
const appName = await createApp(pullRequest)
|
||||
core.setOutput('app_name', appName)
|
||||
} catch(err) {
|
||||
console.log(`Failed to create app: ${err}`)
|
||||
throw(err)
|
||||
}
|
||||
|
||||
- name: Extract user-changes to tmp directory
|
||||
run: |
|
||||
tar -x --file=app.tar -C ${{ runner.temp }}/
|
||||
|
||||
- name: Build, tag, push, and release the Docker image
|
||||
run: |
|
||||
docker image build -f ${{ runner.temp }}/Dockerfile --target production_early_access -t registry.heroku.com/${{ steps.create-app.outputs.app_name}}/web ${{ runner.temp }}
|
||||
heroku container:login
|
||||
docker push registry.heroku.com/${{ steps.create-app.outputs.app_name }}/web
|
||||
heroku container:release web --app=${{ steps.create-app.outputs.app_name }}
|
||||
|
||||
# This command will fail for a brand new app since they don't have a
|
||||
# process type yet so we need to scale after `heroku container:release`.
|
||||
- name: Scale Heroku App size
|
||||
run: |
|
||||
heroku ps:scale --app=${{ steps.create-app.outputs.app_name}} web=1:Standard-2X
|
||||
|
||||
# https://devcenter.heroku.com/articles/container-registry-and-runtime#getting-a-docker-image-id
|
||||
- name: Get Docker Image ID
|
||||
id: image-id
|
||||
run: |
|
||||
echo "::set-output name=image_id::$(docker image inspect registry.heroku.com/${{ steps.create-app.outputs.app_name }}/web --format={{.Id}})"
|
||||
exit 1 # Stop at this point, don't move on to prepare job
|
||||
|
||||
- name: Send Slack notification if workflow fails
|
||||
uses: someimportantcompany/github-actions-slack-message@0b470c14b39da4260ed9e3f9a4f1298a74ccdefd
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
channel: ${{ secrets.DOCS_STAGING_DEPLOYMENT_FAILURES_SLACK_CHANNEL_ID }}
|
||||
bot-token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
|
||||
color: failure
|
||||
text: Staging preparation (docker) failed for PR ${{ env.PR_URL }} at commit ${{ github.sha }}. See https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
|
||||
check-pr-before-deploy:
|
||||
needs: prepare
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 1
|
||||
concurrency:
|
||||
group: staging_docker_${{ github.event.workflow_run.head_branch }}
|
||||
cancel-in-progress: true
|
||||
outputs:
|
||||
pull_request_state: ${{ steps.check-pr.outputs.state }}
|
||||
steps:
|
||||
- name: Check pull request state
|
||||
id: check-pr
|
||||
uses: actions/github-script@2b34a689ec86a68d8ab9478298f91d5401337b7d
|
||||
with:
|
||||
script: |
|
||||
const { owner, repo } = context.repo
|
||||
const { data: pullRequest } = await github.pulls.get({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: ${{ github.event.workflow_run.pull_requests[0].number }}
|
||||
})
|
||||
core.setOutput('state', pullRequest.state)
|
||||
|
||||
deploy:
|
||||
needs: [prepare, check-pr-before-deploy]
|
||||
if: ${{ needs.check-pr-before-deploy.outputs.pull_request_state == 'open' }}
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
concurrency:
|
||||
group: staging_docker_${{ github.event.workflow_run.head_branch }}
|
||||
cancel-in-progress: true
|
||||
steps:
|
||||
- name: Check out repo's default branch
|
||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@38d90ce44d5275ad62cc48384b3d8a58c500bb5f
|
||||
with:
|
||||
node-version: 16.8.x
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install one-off development-only dependencies
|
||||
run: npm install --no-save --include=optional esm
|
||||
|
||||
- name: Deploy
|
||||
id: deploy
|
||||
uses: actions/github-script@2b34a689ec86a68d8ab9478298f91d5401337b7d
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
HEROKU_API_TOKEN: ${{ secrets.HEROKU_API_TOKEN }}
|
||||
HYDRO_ENDPOINT: ${{ secrets.HYDRO_ENDPOINT }}
|
||||
HYDRO_SECRET: ${{ secrets.HYDRO_SECRET }}
|
||||
PR_URL: ${{ env.PR_URL }}
|
||||
SOURCE_BLOB_URL: ${{ needs.prepare.outputs.source_blob_url }}
|
||||
APP_NAME: ${{ needs.prepare.outputs.app_name }}
|
||||
DOCKER_IMAGE_ID: ${{ needs.prepare.outputs.docker_image_id }}
|
||||
with:
|
||||
script: |
|
||||
const { GITHUB_TOKEN, HEROKU_API_TOKEN } = process.env
|
||||
|
||||
// Exit if GitHub Actions PAT is not found
|
||||
if (!GITHUB_TOKEN) {
|
||||
throw new Error('You must supply a GITHUB_TOKEN environment variable!')
|
||||
}
|
||||
|
||||
// Exit if Heroku API token is not found
|
||||
if (!HEROKU_API_TOKEN) {
|
||||
throw new Error('You must supply a HEROKU_API_TOKEN environment variable!')
|
||||
}
|
||||
|
||||
// Workaround to allow us to load ESM files with `require(...)`
|
||||
const esm = require('esm')
|
||||
require = esm({})
|
||||
|
||||
const { default: parsePrUrl } = require('./script/deployment/parse-pr-url')
|
||||
const { default: getOctokit } = require('./script/helpers/github')
|
||||
const { default: deployToStaging } = require('./script/deployment/deploy-to-staging-docker')
|
||||
|
||||
// This helper uses the `GITHUB_TOKEN` implicitly!
|
||||
// We're using our usual version of Octokit vs. the provided `github`
|
||||
// instance to avoid versioning discrepancies.
|
||||
const octokit = getOctokit()
|
||||
|
||||
try {
|
||||
const { PR_URL, SOURCE_BLOB_URL } = process.env
|
||||
const { owner, repo, pullNumber } = parsePrUrl(PR_URL)
|
||||
if (!owner || !repo || !pullNumber) {
|
||||
throw new Error(`'pullRequestUrl' input must match URL format 'https://github.com/github/(docs|docs-internal)/pull/123' but was '${PR_URL}'`)
|
||||
}
|
||||
|
||||
const { data: pullRequest } = await octokit.pulls.get({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: pullNumber
|
||||
})
|
||||
|
||||
await deployToStaging({
|
||||
octokit,
|
||||
pullRequest,
|
||||
forceRebuild: false,
|
||||
// These parameters will ONLY be set by Actions
|
||||
sourceBlobUrl: SOURCE_BLOB_URL,
|
||||
runId: context.runId
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(`Failed to deploy to staging: ${error.message}`)
|
||||
console.error(error)
|
||||
throw error
|
||||
}
|
||||
|
||||
- name: Mark the deployment as inactive if timed out
|
||||
if: ${{ steps.deploy.outcome == 'cancelled' }}
|
||||
uses: actions/github-script@2b34a689ec86a68d8ab9478298f91d5401337b7d
|
||||
with:
|
||||
script: |
|
||||
// TODO: Find the relevant deployment
|
||||
// TODO: Create a new deployment status for it as "inactive"
|
||||
return 'TODO'
|
||||
|
||||
- name: Send Slack notification if workflow fails
|
||||
uses: someimportantcompany/github-actions-slack-message@0b470c14b39da4260ed9e3f9a4f1298a74ccdefd
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
channel: ${{ secrets.DOCS_STAGING_DEPLOYMENT_FAILURES_SLACK_CHANNEL_ID }}
|
||||
bot-token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }}
|
||||
color: failure
|
||||
text: Staging deployment failed for PR ${{ env.PR_URL }} at commit ${{ github.sha }}. See https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
Reference in New Issue
Block a user