commit
6171a012db
@ -0,0 +1,43 @@
|
||||
# fly-deploy will be triggered on completion of this workflow to actually deploy the code to fly.io.
|
||||
|
||||
name: fly.io Build
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
types: [labeled, opened, synchronize, reopened]
|
||||
|
||||
# Allows running this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build Docker image
|
||||
runs-on: ubuntu-latest
|
||||
# Build when the 'preview' label is added, or when PR is updated with this label present.
|
||||
if: >
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event_name == 'pull_request' &&
|
||||
contains(github.event.pull_request.labels.*.name, 'preview'))
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build and export Docker image
|
||||
id: docker-build
|
||||
run: >
|
||||
docker build -t grist-core:preview . &&
|
||||
docker image save grist-core:preview -o grist-core.tar
|
||||
- name: Save PR information
|
||||
run: |
|
||||
echo PR_NUMBER=${{ github.event.number }} >> ./pr-info.txt
|
||||
echo PR_SOURCE=${{ github.event.pull_request.head.repo.full_name }}-${{ github.event.pull_request.head.ref }} >> ./pr-info.txt
|
||||
echo PR_SHASUM=${{ github.event.pull_request.head.sha }} >> ./pr-info.txt
|
||||
# PR_SOURCE looks like <owner>/<repo>-<branch>.
|
||||
# For example, if the GitHub user "foo" forked grist-core as "grist-bar", and makes a PR from their branch named "baz",
|
||||
# it will be "foo/grist-bar-baz". deploy.js later replaces "/" with "-", making it "foo-grist-bar-baz".
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: docker-image
|
||||
path: |
|
||||
./grist-core.tar
|
||||
./pr-info.txt
|
||||
if-no-files-found: "error"
|
@ -0,0 +1,70 @@
|
||||
# Follow-up of fly-build, with access to secrets for making deployments.
|
||||
# This workflow runs in the target repo context. It does not, and should never execute user-supplied code.
|
||||
# See https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
|
||||
|
||||
name: fly.io Deploy
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["fly.io Build"]
|
||||
types:
|
||||
- completed
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy app to fly.io
|
||||
runs-on: ubuntu-latest
|
||||
if: |
|
||||
github.event.workflow_run.event == 'pull_request' &&
|
||||
github.event.workflow_run.conclusion == 'success'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up flyctl
|
||||
uses: superfly/flyctl-actions/setup-flyctl@master
|
||||
with:
|
||||
version: 0.2.72
|
||||
- name: Download artifacts
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
var artifacts = await github.rest.actions.listWorkflowRunArtifacts({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
run_id: ${{ github.event.workflow_run.id }},
|
||||
});
|
||||
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
|
||||
return artifact.name == "docker-image"
|
||||
})[0];
|
||||
var download = await github.rest.actions.downloadArtifact({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
artifact_id: matchArtifact.id,
|
||||
archive_format: 'zip',
|
||||
});
|
||||
var fs = require('fs');
|
||||
fs.writeFileSync('${{github.workspace}}/docker-image.zip', Buffer.from(download.data));
|
||||
- name: Extract artifacts
|
||||
id: extract_artifacts
|
||||
run: |
|
||||
unzip docker-image.zip
|
||||
cat ./pr-info.txt >> $GITHUB_OUTPUT
|
||||
- name: Load Docker image
|
||||
run: docker load --input grist-core.tar
|
||||
- name: Deploy to fly.io
|
||||
id: fly_deploy
|
||||
env:
|
||||
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
||||
BRANCH_NAME: ${{ steps.extract_artifacts.outputs.PR_SOURCE }}
|
||||
run: |
|
||||
node buildtools/fly-deploy.js deploy
|
||||
flyctl config -c ./fly.toml env | awk '/APP_HOME_URL/{print "DEPLOY_URL=" $2}' >> $GITHUB_OUTPUT
|
||||
flyctl config -c ./fly.toml env | awk '/FLY_DEPLOY_EXPIRATION/{print "EXPIRES=" $2}' >> $GITHUB_OUTPUT
|
||||
- name: Comment on PR
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: ${{ steps.extract_artifacts.outputs.PR_NUMBER }},
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: `Deployed commit \`${{ steps.extract_artifacts.outputs.PR_SHASUM }}\` as ${{ steps.fly_deploy.outputs.DEPLOY_URL }} (until ${{ steps.fly_deploy.outputs.EXPIRES }})`
|
||||
})
|
@ -0,0 +1,36 @@
|
||||
# This workflow runs in the target repo context, as it is triggered via pull_request_target.
|
||||
# It does not, and should not have access to code in the PR.
|
||||
# See https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
|
||||
|
||||
name: fly.io Destroy
|
||||
on:
|
||||
pull_request_target:
|
||||
branches: [ main ]
|
||||
types: [unlabeled, closed]
|
||||
|
||||
# Allows running this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
destroy:
|
||||
name: Remove app from fly.io
|
||||
runs-on: ubuntu-latest
|
||||
# Remove the deployment when 'preview' label is removed, or the PR is closed.
|
||||
if: |
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event_name == 'pull_request_target' &&
|
||||
(github.event.action == 'closed' ||
|
||||
(github.event.action == 'unlabeled' && github.event.label.name == 'preview')))
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up flyctl
|
||||
uses: superfly/flyctl-actions/setup-flyctl@master
|
||||
with:
|
||||
version: 0.2.72
|
||||
- name: Destroy fly.io app
|
||||
env:
|
||||
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
||||
BRANCH_NAME: ${{ github.event.pull_request.head.repo.full_name }}-${{ github.event.pull_request.head.ref }}
|
||||
# See fly-build for what BRANCH_NAME looks like.
|
||||
id: fly_destroy
|
||||
run: node buildtools/fly-deploy.js destroy
|
@ -1,64 +0,0 @@
|
||||
name: Fly Deploy
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
types: [labeled, unlabeled, closed, opened, synchronize, reopened]
|
||||
|
||||
# Allows running this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
||||
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Deploy app
|
||||
runs-on: ubuntu-latest
|
||||
# Deploy when the 'preview' label is added, or when PR is updated with this label present.
|
||||
if: |
|
||||
github.repository_owner == 'gristlabs' &&
|
||||
github.event_name == 'pull_request' && (
|
||||
github.event.action == 'labeled' ||
|
||||
github.event.action == 'opened' ||
|
||||
github.event.action == 'synchronize' ||
|
||||
github.event.action == 'reopened'
|
||||
) &&
|
||||
contains(github.event.pull_request.labels.*.name, 'preview')
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: superfly/flyctl-actions/setup-flyctl@master
|
||||
with:
|
||||
version: 0.1.89
|
||||
- id: fly_deploy
|
||||
run: |
|
||||
node buildtools/fly-deploy.js deploy
|
||||
flyctl config -c ./fly.toml env | awk '/APP_HOME_URL/{print "DEPLOY_URL=" $2}' >> $GITHUB_OUTPUT
|
||||
flyctl config -c ./fly.toml env | awk '/FLY_DEPLOY_EXPIRATION/{print "EXPIRES=" $2}' >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: `Deployed as ${{ steps.fly_deploy.outputs.DEPLOY_URL }} (until ${{ steps.fly_deploy.outputs.EXPIRES }})`
|
||||
})
|
||||
|
||||
destroy:
|
||||
name: Remove app
|
||||
runs-on: ubuntu-latest
|
||||
# Remove the deployment when 'preview' label is removed, or the PR is closed.
|
||||
if: |
|
||||
github.repository_owner == 'gristlabs' &&
|
||||
github.event_name == 'pull_request' &&
|
||||
(github.event.action == 'closed' ||
|
||||
(github.event.action == 'unlabeled' && github.event.label.name == 'preview'))
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: superfly/flyctl-actions/setup-flyctl@master
|
||||
with:
|
||||
version: 0.1.89
|
||||
- id: fly_destroy
|
||||
run: node buildtools/fly-deploy.js destroy
|
@ -0,0 +1,27 @@
|
||||
import moment from 'moment-timezone';
|
||||
|
||||
/**
|
||||
* Output an ISO8601 format datetime string, with timezone.
|
||||
* Any string fed in without timezone is expected to be in UTC.
|
||||
*
|
||||
* When connected to postgres, dates will be extracted as Date objects,
|
||||
* with timezone information. The normalization done here is not
|
||||
* really needed in this case.
|
||||
*
|
||||
* Timestamps in SQLite are stored as UTC, and read as strings
|
||||
* (without timezone information). The normalization here is
|
||||
* pretty important in this case.
|
||||
*/
|
||||
export function normalizedDateTimeString(dateTime: any): string {
|
||||
if (!dateTime) { return dateTime; }
|
||||
if (dateTime instanceof Date) {
|
||||
return moment(dateTime).toISOString();
|
||||
}
|
||||
if (typeof dateTime === 'string' || typeof dateTime === 'number') {
|
||||
// When SQLite returns a string, it will be in UTC.
|
||||
// Need to make sure it actually have timezone info in it
|
||||
// (will not by default).
|
||||
return moment.utc(dateTime).toISOString();
|
||||
}
|
||||
throw new Error(`normalizedDateTimeString cannot handle ${dateTime}`);
|
||||
}
|
Loading…
Reference in new issue