Skip to content

Merge pull request #193 from expressvpn/gha/cargo-update #88

Merge pull request #193 from expressvpn/gha/cargo-update

Merge pull request #193 from expressvpn/gha/cargo-update #88

Workflow file for this run

name: Release
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
branches: [main]
paths:
- "*/Cargo.toml" # only run if Cargo.toml changes
push:
branches: [main]
paths:
- "*/Cargo.toml" # only run if Cargo.toml changes
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref_name != 'main' }}
jobs:
check_label:
runs-on: ubuntu-latest
outputs:
is_release_label_attached: ${{ steps.pr_labels.outputs.is_release_label_attached }}
issue_number: ${{ steps.pr_labels.outputs.issue_number }}
steps:
- uses: jwalton/gh-find-current-pr@v1
id: find_pr
with:
state: all
- id: pr_labels
uses: actions/github-script@v7
with:
script: |
const issue_number = ${{ steps.find_pr.outputs.pr }}
const owner = context.repo.owner
const repo = context.repo.repo
const attached_labels = (await github.rest.issues.listLabelsOnIssue({ owner, repo, issue_number })).data
const is_release_label_attached = attached_labels.find(label => label.name == 'release') !== undefined
core.setOutput('is_release_label_attached', is_release_label_attached)
core.setOutput('issue_number', issue_number)
release:
runs-on: ubuntu-latest
needs: check_label
if: needs.check_label.outputs.is_release_label_attached == 'true'
env:
EARTHLY_TOKEN: "${{ secrets.EARTHLY_TOKEN }}"
CARGO_REGISTRY_TOKEN: "${{ secrets.CARGO_REGISTRY_TOKEN }}"
FORCE_COLOR: 1
steps:
- uses: earthly/actions-setup@v1
with:
version: v0.8.3
github-token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/checkout@v4
with:
submodules: true
- name: Check Cargo.lock is up-to-date
shell: bash
run: |
cargo update --workspace
diff=$(git status --porcelain --ignore-submodules=none -- Cargo.lock)
if [ -n "$diff" ] ; then
echo "ERROR: git tree is dirty!"
echo
echo "$diff"
echo
git --no-pager diff -- $paths
exit 1
fi
- uses: ./.github/actions/check-dependencies
id: check_dependencies
- uses: actions/setup-node@v4
with:
node-version: '20.x'
- run: npm install toml globby axios
- uses: actions/github-script@v7
id: process_release
with:
script: |
const is_merge_event = ${{ github.event_name == 'push' }}
const { globby } = await import("${{ github.workspace }}/node_modules/globby/index.js");
const toml = require('toml');
const fs = require('fs');
const axios = require('axios');
const { spawn } = require('child_process');
async function get_crate_details(name) {
try {
return (await axios(`https://crates.io/api/v1/crates/${name}`)).data
} catch(error) {
console.error("Error fetching crate details:", error)
}
}
function run_command_stream(command_arr) {
const [command, ...args] = command_arr
const child = spawn(command, args)
child.stdout.on("data", data => console.log(Buffer.from(data).toString()))
child.stderr.on("data", data => console.error(Buffer.from(data).toString()))
child.on("close", code => {
console.log(command, "exited with code", code)
if (code != 0) {
core.setFailed(`Command ${command_str} exited with code ${code}`)
}
})
}
async function create_github_release(crate_name, version) {
console.log("Creating a github release for", crate_name, version)
const { owner, repo } = context.repo
const tag_name = `${crate_name}-${version}`
const name = tag_name
const ref = `refs/tags/${tag_name}`
const sha = context.sha
const body = ""
await github.rest.git.createRef({ owner, repo, ref, sha })
await github.rest.repos.createRelease({ owner, repo, tag_name, name, body })
}
const is_dry_run = !is_merge_event
console.log("Dry run status is:", is_dry_run)
console.log("wolfssl-sys release version: ${{ steps.check_dependencies.outputs.wolfssl_sys_release_version }}")
console.log("wolfssl-sys release status: ${{ steps.check_dependencies.outputs.wolfssl_sys_release_status }}")
console.log("wolfssl to sys dependency version: ${{ steps.check_dependencies.outputs.dependency_version }}")
console.log("wolfssl to sys dependency status: ${{ steps.check_dependencies.outputs.dependency_status }}")
const crate_definitions = await globby("*/Cargo.toml")
let blocked_crates = 0
const crates_to_publish = []
for (crate_definition of crate_definitions) {
console.log(`Processing ${crate_definition}`)
const data = toml.parse(fs.readFileSync(crate_definition, 'utf8'))
const name = data.package.name
const version_on_file = data.package.version
const version_on_crate_io = (await get_crate_details(name)).crate.max_version
if (version_on_file != version_on_crate_io) {
console.log("Crate", name, "needs publishing")
if (name == "wolfssl" && "${{ steps.check_dependencies.outputs.dependency_status }}" != "released") {
console.log("Cannot release wolfssl with dependency on ${{ steps.check_dependencies.outputs.dependency_status }} wolfssl-sys")
crates_to_publish.push({ name, version_on_file, version_on_crate_io, release_status: ":no_entry: Blocked", comment: "dependency on ${{ steps.check_dependencies.outputs.dependency_status }} wolfssl-sys" })
blocked_crates++
continue
}
crates_to_publish.push({ name, version_on_file, version_on_crate_io, release_status: ":white_check_mark: Release", comment: "" })
run_command_stream(["earthly", "--push", "--ci", "--org", "expressvpn", "--satellite", "wolfssl-rs", "--secret", "CARGO_REGISTRY_TOKEN", "+publish", `--PACKAGE=${name}`, `--DRY_RUN=${is_dry_run}`])
if (!is_dry_run) {
await create_github_release(name, version_on_file)
}
} else {
console.log("Crate", name, "does not need publishing")
crates_to_publish.push({ name, version_on_file, version_on_crate_io, release_status: ":negative_squared_cross_mark: No release", comment: "" })
}
}
const crates_to_publish_table_rendered = crates_to_publish.map(item =>
`| \`${item.name}\` | ${item.version_on_crate_io} | **${item.version_on_file}** | ${item.release_status} | ${item.comment} | `
).join("\n")
core.setOutput('crates_to_publish_table_rendered', crates_to_publish_table_rendered)
core.setOutput('nr_crates_blocked_from_releasing', blocked_crates)
- uses: peter-evans/find-comment@v3
id: fc
with:
issue-number: ${{ needs.check_label.outputs.issue_number }}
comment-author: 'github-actions[bot]'
body-includes: Release Plan
- if: steps.process_release.outputs.crates_to_publish_table_rendered == ''
uses: peter-evans/create-or-update-comment@v4
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ needs.check_label.outputs.issue_number }}
body: |
# :rocket: Release Plan
Nothing to release
edit-mode: replace
- if: steps.process_release.outputs.crates_to_publish_table_rendered != ''
uses: peter-evans/create-or-update-comment@v4
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ needs.check_label.outputs.issue_number }}
body: |
# :rocket: Release Plan
| Crate | Previous version | New version | Release? | Comment |
|--|--|--|--|--|
${{ steps.process_release.outputs.crates_to_publish_table_rendered }}
edit-mode: replace
- if: ${{ steps.process_release.outputs.nr_crates_blocked_from_releasing > 0 }}
name: Fail release on blocked releases
uses: actions/github-script@v7
with:
script: core.setFailed('Unable to release all required crates')