Create Deployment #648
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Create Deployment | |
on: | |
workflow_dispatch: | |
inputs: | |
force: | |
description: "Force deployment of all services, regardless of changes" | |
required: true | |
type: boolean | |
default: false | |
environment: | |
description: "Environment" | |
required: true | |
default: "production" | |
type: choice | |
options: | |
- production | |
jobs: | |
detect_changes: | |
name: Determine service updates | |
runs-on: ubuntu-latest | |
outputs: | |
migrations: ${{ steps.changes.outputs.migrations }} | |
api: ${{ steps.changes.outputs.api }} | |
client: ${{ steps.changes.outputs.client }} | |
build_label: ${{ steps.short_sha.outputs.sha }} | |
steps: | |
- uses: actions/checkout@v4 | |
with: | |
fetch-depth: 0 | |
- id: short_sha | |
run: | | |
echo `git rev-parse HEAD` | |
echo "::set-output name=sha::`git rev-parse --short HEAD`" | |
- uses: actions/github-script@v4 | |
id: deployments | |
with: | |
script: | | |
const query = `query($owner: String!, $repo: String!, $env: [String!]) { | |
repository(owner: $owner, name: $repo) { | |
deployments(environments: $env, last: 100) { | |
nodes { | |
commitOid | |
state | |
} | |
} | |
} | |
}`; | |
const variables = { | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
env: ['production_client', 'production_server', 'production_db_migrations'] | |
} | |
const data = await github.graphql(query, variables); | |
console.log(data); | |
const mostRecentActiveDeployment = data.repository.deployments.nodes.reverse().find(({state}) => state == 'ACTIVE'); | |
if (mostRecentActiveDeployment) { | |
core.setOutput('active_ref', mostRecentActiveDeployment.commitOid); | |
console.log('base:', mostRecentActiveDeployment.commitOid); | |
} | |
console.log('ref:', '${{github.ref}}'); | |
- uses: dorny/paths-filter@v2 | |
name: get changed packages | |
id: changes | |
with: | |
base: ${{ steps.deployments.outputs.active_ref }} | |
filters: | | |
api: | |
- 'packages/api/**' | |
client: | |
- 'packages/client/**' | |
migrations: | |
- 'packages/api/migrations/committed/**' | |
infra: | |
- 'packages/infra/**' | |
unmanaged_packages: | |
- 'packages/!(api|client|infra)/**' | |
run_migrations: | |
name: Run DB Migrations | |
if: github.event.inputs.force == 'true' || needs.detect_changes.outputs.migrations == 'true' | |
concurrency: ${{github.event.inputs.environment }}_db_migrations | |
timeout-minutes: 5 | |
runs-on: ubuntu-latest | |
environment: | |
name: ${{github.event.inputs.environment }}_db_migrations | |
url: https://api.seasket.ch/graphiql | |
needs: | |
- detect_changes | |
steps: | |
- name: Configure AWS credentials | |
uses: aws-actions/configure-aws-credentials@v1 | |
with: | |
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
aws-region: us-west-2 | |
- name: Install unbuffer command | |
run: | | |
sudo apt-get install expect | |
# This remote command must succeed with the message "database migrations complete" | |
- name: Run migrations using ecs exec | |
run: | | |
unbuffer aws ecs execute-command --cluster ${{secrets.MAINTENANCE_STACK}} --task ${{secrets.MAINTENANCE_TASK}} --container Default --command "/bin/sh -l /home/migrate.sh ${{github.sha}}" --interactive | tee /dev/stderr | grep "database migrations complete" | |
deploy_server: | |
runs-on: ubuntu-latest | |
name: Deploy Server | |
if: always() && needs.run_migrations.result != 'failure' && (github.event.inputs.force == 'true' || needs.detect_changes.outputs.api == 'true') | |
concurrency: ${{github.event.inputs.environment }}_server | |
timeout-minutes: 25 | |
environment: | |
name: ${{github.event.inputs.environment }}_server | |
url: https://api.seasket.ch/graphiql | |
needs: | |
- detect_changes | |
- run_migrations | |
steps: | |
- uses: actions/checkout@v4 | |
- name: Configure AWS credentials | |
uses: aws-actions/configure-aws-credentials@v1 | |
with: | |
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
aws-region: us-west-2 | |
- uses: actions/setup-node@v3 | |
with: | |
node-version: "18.17" | |
cache: "npm" | |
- name: Install CDK | |
run: | | |
npm install -g aws-cdk typescript@5.5.3 | |
- name: Install dependencies | |
run: | | |
npx lerna@6.6.2 bootstrap --scope=infra | |
- name: Deploy server | |
working-directory: ./packages/infra | |
env: | |
AUTH0_CLIENT_SECRET: ${{ secrets.AUTH0_CLIENT_SECRET }} | |
AUTH0_CLIENT_ID: ${{ secrets.AUTH0_CLIENT_ID }} | |
BUILD: ${{ needs.detect_changes.outputs.build_label }} | |
COMMIT: ${{ needs.detect_changes.outputs.build_label }} | |
UNSPLASH_KEY: ${{ secrets.UNSPLASH_KEY }} | |
SENTRY_DSN: ${{ secrets.SENTRY_DSN }} | |
MAPBOX_ACCESS_TOKEN: ${{ secrets.MAPBOX_ACCESS_TOKEN }} | |
R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} | |
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} | |
R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }} | |
R2_FILE_UPLOADS_BUCKET: ${{ secrets.R2_FILE_UPLOADS_BUCKET }} | |
R2_TILES_BUCKET: ${{ secrets.R2_TILES_BUCKET }} | |
CLOUDFLARE_IMAGES_ACCOUNT: ${{ secrets.CLOUDFLARE_IMAGES_ACCOUNT }} | |
CLOUDFLARE_IMAGES_TOKEN: ${{ secrets.CLOUDFLARE_IMAGES_TOKEN }} | |
CLOUDFLARE_IMAGES_ACCOUNT_HASH: ${{ secrets.CLOUDFLARE_IMAGES_ACCOUNT_HASH }} | |
SCREENSHOTTER_FUNCTION_ARN: ${{ secrets.SCREENSHOTTER_FUNCTION_ARN }} | |
UPLOADS_BASE_URL: ${{ secrets.UPLOADS_BASE_URL }} | |
RESOURCES_REMOTE: ${{ secrets.RESOURCES_REMOTE }} | |
TILES_REMOTE: ${{ secrets.TILES_REMOTE }} | |
TILES_BASE_URL: ${{ secrets.TILES_BASE_URL }} | |
CLOUDFLARE_ACCOUNT_TAG: ${{ secrets.CLOUDFLARE_ACCOUNT_TAG }} | |
CLOUDFLARE_GRAPHQL_TOKEN: ${{ secrets.CLOUDFLARE_GRAPHQL_TOKEN }} | |
CLOUDFLARE_SITE_TAG: ${{ secrets.CLOUDFLARE_SITE_TAG }} | |
PMTILES_SERVER_ZONE: ${{ secrets.PMTILES_SERVER_ZONE }} | |
SLACK_CHANNEL: ${{ secrets.SLACK_CHANNEL }} | |
SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} | |
run: | | |
cdk deploy --require-approval never -e SeaSketchGraphQLServer | |
deploy_client: | |
name: Deploy Client | |
runs-on: ubuntu-latest | |
timeout-minutes: 15 | |
if: github.event.inputs.force == 'true' || needs.detect_changes.outputs.client == 'true' | |
concurrency: ${{github.event.inputs.environment }}_client | |
environment: | |
name: ${{github.event.inputs.environment}}_client | |
url: https://www.seasketch.org/ | |
needs: | |
- detect_changes | |
steps: | |
- uses: actions/checkout@v4 | |
- uses: actions/setup-node@v2 | |
with: | |
node-version: "18.17" | |
cache: "npm" | |
- name: Install dependencies | |
run: | | |
npx lerna@6.6.2 bootstrap --ci | |
- name: Build client | |
working-directory: ./packages/client | |
env: | |
SKIP_PREFLIGHT_CHECK: true | |
REACT_APP_AUTH0_CLIENT_ID: ${{ secrets.REACT_APP_AUTH0_CLIENT_ID }} | |
REACT_APP_AUTH0_DOMAIN: ${{ secrets.REACT_APP_AUTH0_DOMAIN }} | |
REACT_APP_AUTH0_SCOPE: "openid profile email permissions" | |
REACT_APP_AUTH0_AUDIENCE: https://api.seasketch.org | |
REACT_APP_MAPBOX_ACCESS_TOKEN: ${{ secrets.REACT_APP_MAPBOX_ACCESS_TOKEN }} | |
REACT_APP_GRAPHQL_ENDPOINT: ${{ secrets.REACT_APP_GRAPHQL_ENDPOINT }} | |
REACT_APP_BUILD: ${{ needs.detect_changes.outputs.build_label }} | |
REACT_APP_SENTRY_DSN: ${{ secrets.REACT_APP_SENTRY_DSN }} | |
REACT_APP_CLOUDFRONT_DOCS_DISTRO: ${{ secrets.REACT_APP_CLOUDFRONT_DOCS_DISTRO }} | |
REACT_APP_CLOUDFLARE_IMAGES_ENDPOINT: ${{ secrets.REACT_APP_CLOUDFLARE_IMAGES_ENDPOINT }} | |
run: | | |
CI=false npm run build | |
- name: Configure AWS credentials | |
uses: aws-actions/configure-aws-credentials@v1 | |
with: | |
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
aws-region: us-west-2 | |
# - name: Update deployment | |
# working-directory: ./packages/client | |
# env: | |
# S3_BUCKET: ${{ secrets.S3_CLIENT_BUCKET}} | |
# run: | | |
# ls -l | |
# if [[ -z "$S3_BUCKET" ]]; then | |
# echo "S3_BUCKET could not be found" 1>&2 | |
# exit 1 | |
# fi | |
# echo "Uploading files to $S3_BUCKET..." | |
# aws s3 sync build $S3_BUCKET \ | |
# --acl public-read \ | |
# --cache-control max-age=31536000 \ | |
# --exclude service-worker.js \ | |
# --exclude manifest.json \ | |
# --exclude index.html \ | |
# --delete | |
# echo "Uploading manifest.json" | |
# aws s3 cp build/manifest.json $S3_BUCKET/manifest.json \ | |
# --metadata-directive REPLACE \ | |
# --cache-control max-age=0,no-cache,no-store,must-revalidate \ | |
# --content-type application/json \ | |
# --acl public-read | |
# echo "Uploading index.html" | |
# aws s3 cp build/index.html $S3_BUCKET/index.html \ | |
# --metadata-directive REPLACE \ | |
# --cache-control max-age=0,no-cache,no-store,must-revalidate \ | |
# --content-type text/html \ | |
# --acl public-read | |
# echo "Uploading service-worker.js" | |
# aws s3 cp build/service-worker.js $S3_BUCKET/service-worker.js \ | |
# --metadata-directive REPLACE \ | |
# --cache-control max-age=300 \ | |
# --content-type text/javascript \ | |
# --acl public-read | |
# aws s3 cp build/service-worker.js.map $S3_BUCKET/service-worker.js.map \ | |
# --metadata-directive REPLACE \ | |
# --cache-control max-age=300 \ | |
# --content-type text/javascript \ | |
# --acl public-read | |
- name: deploy to cloudflare pages | |
id: wrangler | |
working-directory: ./packages/client | |
env: | |
CLOUDFLARE_ACCOUNT_ID: ${{secrets.CLOUDFLARE_ACCOUNT_ID}} | |
CLOUDFLARE_API_TOKEN: ${{secrets.CLOUDFLARE_API_TOKEN}} | |
run: | | |
npx wrangler pages publish build --project-name=seasketch-next-client --commit-dirty=true --branch=main | |
- name: Create Sentry release | |
uses: getsentry/action-release@v1 | |
env: | |
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} | |
SENTRY_ORG: ${{ secrets.SENTRY_ORG }} | |
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} | |
with: | |
environment: production | |
version: ${{ needs.detect_changes.outputs.build_label }} | |
sourcemaps: packages/client/build/static/js/ |