Skip to content

Commit

Permalink
Consolidate and update scripts (#9)
Browse files Browse the repository at this point in the history
* Extract upsert-pending-release into js file
* Simplify main action to append prereleases
* Attempt refactor to new flow
* Fix concurrency comments
  • Loading branch information
danielemery authored Mar 10, 2024
1 parent 3ef8ebe commit 700c693
Show file tree
Hide file tree
Showing 7 changed files with 235 additions and 213 deletions.
89 changes: 0 additions & 89 deletions .github/workflows/deploy-existing-release.yml

This file was deleted.

50 changes: 50 additions & 0 deletions .github/workflows/deploy-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Deploy Existing Release

on:
workflow_dispatch:

# Ensure only one instance of either this is running at a time.
# This ensures that we don't put production into an inconsistent state.
concurrency:
group: 'prod-deployment'

jobs:
deploy:
runs-on: ubuntu-latest
permissions:
packages: write
contents: write # Write is required to create/update releases
steps:
- name: Perform pre-deployment checks
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const script = require('./scripts/validate-prerelease.js');
const result = await script({github, context, core}, ${{github.ref_name}});
console.log('Validation complete');
console.log(result);
return result;
- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Deploy to Production
# To emulate deployments here we are simply shifting the latest tag to the appropriate docker image.
# In a real world scenario, you would replace this with your actual deployment steps.
run: |
docker pull ghcr.io/${{ github.repository }}:${{github.ref_name}}
docker tag ghcr.io/${{ github.repository }}:${{github.ref_name}} ghcr.io/${{ github.repository }}:latest
docker push ghcr.io/${{ github.repository }}:latest
- name: Update latest pointer
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const script = require('./scripts/reconcile-release.js');
await script({github, context, core}, ${{ steps.validate_deployment.outputs.result.releaseId }});
45 changes: 5 additions & 40 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ on:
push:
branches: ["main"]

# Ensure only one instance of the workflow is running at a time
# This helps with race conditions when upserting releases
# Ensure only one instance of the workflow is running at a time.
# This helps with race conditions when upserting releases.
concurrency:
group: 'main'
group: "main"

env:
REGISTRY: ghcr.io
Expand Down Expand Up @@ -42,45 +42,10 @@ jobs:
build-args: |
APP_VERSION=${{ env.RELEASE_VERSION }}
- name: Create tag
uses: rickstaa/action-create-tag@v1
id: "tag_create"
with:
tag: ${{ env.RELEASE_VERSION }}

- name: Upsert pending release
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { owner, repo } = context.repo;
/*
* We should only need to load 2 releases, as either both will be latest, or one will be latest and
* the other will be pending. We're loading a few extras here so that if we get into a weird state
* we can provide a better error.
*/
const PAGE_SIZE = 10;
const releases = await github.rest.repos.listReleases({
owner,
repo,
per_page: PAGE_SIZE,
});
const pendingReleases = releases.data.filter(release => release.prerelease);
if (pendingReleases.length > 1) {
throw new Error(`Found more than one pending release: ${pendingReleases.map(release => release.tag_name).join(', ')}`);
}
if (pendingReleases.length === 1) {
console.log(`Found existing pending release: ${pendingReleases[0].tag_name}, replacing it`);
await github.rest.repos.deleteRelease({ owner, repo, release_id: pendingReleases[0].id });
}
const newRelease = await github.rest.repos.createRelease({
owner,
repo,
prerelease: true,
tag_name: "${{ env.RELEASE_VERSION }}",
name: "${{ env.RELEASE_VERSION }}",
generate_release_notes: true,
});
console.log(`Created pending release: ${newRelease.data.html_url}`);
const createPrerelease = require('./scripts/create-prerelease.js');
await createPrerelease({github, context, core}, "${{ env.RELEASE_VERSION }}");
84 changes: 0 additions & 84 deletions .github/workflows/publish-new-release.yml

This file was deleted.

21 changes: 21 additions & 0 deletions .github/workflows/scripts/create-prerelease.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module.exports = async ({ github, context }, tag) => {
const { owner, repo } = context.repo;

await github.rest.git.createRef({
owner,
repo,
ref: `refs/tags/${tag}`,
sha: context.sha,
});

const newRelease = await github.rest.repos.createRelease({
owner,
repo,
prerelease: true,
tag_name: tag,
name: tag,
generate_release_notes: true,
});

console.log(`Created prerelease: ${newRelease.data.html_url}`);
};
89 changes: 89 additions & 0 deletions .github/workflows/scripts/reconcile-releases.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/** The maximum number of release pages to search through to find prereleases to be cleaned up. */
const MAX_PAGE_SEARCH = 5;

module.exports = async ({ github, context }, targetReleaseId) => {
const { owner, repo } = context.repo;

const targetRelease = await github.rest.repos.getRelease({
owner,
repo,
release_id: targetReleaseId,
});

if (targetRelease.data.draft) {
console.log("Target is a draft release, finding prereleases to bundle up");

// Collect all prereleases
let prereleases = [];
const releasesIterator = github.paginate.iterator(
github.rest.repos.listReleases,
{
owner,
repo,
}
);
while (!result.done && currentPage <= MAX_PAGE_SEARCH) {
prereleases = prereleases.concat(
result.value.data.filter((release) => release.prerelease)
);
result = await releasesIterator.next();
}

// Determine which prereleases are older than the target release
let newerPreleaseCount = 0;
const olderPreleases = [];
for (const prerelease of prereleases) {
const diff = await github.rest.repos.compareCommitsWithBasehead({
owner,
repo,
basehead: `${prerelease.tag_name}...${targetRelease.data.tag_name}`,
});

if (diff.data.ahead_by > 0) {
console.log(
`Prerelease ${prerelease.tag_name} is newer than target release, skipping`
);
newerPreleaseCount++;
continue;
} else {
console.log(
`Prerelease ${prerelease.tag_name} is older than target release, adding to cleanup list`
);
olderPreleases.push(prerelease);
}
}

console.log(
`Found ${olderPreleases.length} older prereleases to cleanup, ${newerPreleaseCount} newer prereleases skipped`
);

// Delete older prereleases
for (const olderPrerelease of olderPreleases) {
await github.rest.repos.deleteRelease({
owner,
repo,
release_id: olderPrerelease.id,
});
}

console.log("Promoting draft release to production");
// Promote draft release to production
await github.rest.repos.updateRelease({
owner,
repo,
release_id: "${{ steps.validate_deployment.outputs.result }}",
draft: false,
prerelease: false,
latest: true,
});
} else {
console.log("Target is an existing release, marking release as latest");

await github.rest.repos.updateRelease({
owner,
repo,
release_id: "${{ steps.validate_deployment.outputs.result }}",
make_latest: true,
});
}
};
Loading

0 comments on commit 700c693

Please sign in to comment.