Skip to content

Commit

Permalink
feat: rust release workflow
Browse files Browse the repository at this point in the history
  • Loading branch information
EstebanBorai committed Nov 4, 2023
1 parent a0f318d commit be75c49
Show file tree
Hide file tree
Showing 2 changed files with 320 additions and 7 deletions.
262 changes: 262 additions & 0 deletions src/mdsvex/rust-github-actions-release-workflow/en.svx
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
---
title: "Rust GitHub Actions Release Workflow"
description: "Setting up a GitHub Actions Workflow to tag and release a Rust Crate via Workflow Dispatch"
categories: [rust, github, cd, ci, automation]
icon: rust
date: 2023-11-04
preview_image_url: "https://unsplash.com/photos/8gr6bObQLOI/download?ixid=M3wxMjA3fDB8MXxzZWFyY2h8NjB8fGF1dG9tYXRpb258ZW58MHx8fHwxNjk5MTI3MTI3fDA&force=true&w=640"
published: true
---

## Motivation

Maintianing many open source packages is time consuming, you have pull requests
to review, security issues to concern about and also patch, open tracking issues
to continuosly improve and also keep an eye on updates and improvements.

This makes any automation improvements a big deal, whenever you can automate
these tasks will allow you to have more time to focus in actual work.

Dependency management can be addressed with Dependabot which is pretty easy to
setup, just setup a `.github/dependabot.yml` with the following contents and
you will get weekly updates on dependencies:

```yml
version: 2
updates:
- package-ecosystem: 'cargo'
directory: '/'
schedule:
interval: 'weekly'
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: 'weekly'
```

This will open a bunch on PRs every monday, so its recommeded to setup a CI
workflow to at least check tests and build integrity.

By having these CI workflows settled, you can then provide a couple workflows
for approving and merging PRs.

The following workflow will approve PRs from dependabot when CI passes:

```yml
name: Dependabot auto-approve
on: pull_request_target

permissions:
pull-requests: write

jobs:
dependabot:
runs-on: ubuntu-latest
if: ${{ github.actor == 'dependabot[bot]' }}
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v1.1.1
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: Approve a PR
run: gh pr review --approve "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

And this other will merge approved PRs:

```yml
name: Dependabot auto-merge
on: pull_request_target

permissions:
pull-requests: write
contents: write

jobs:
dependabot:
runs-on: ubuntu-latest
if: ${{ github.actor == 'dependabot[bot]' }}
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v1.1.1
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
- name: Enable auto-merge for Dependabot PRs
if: ${{steps.metadata.outputs.update-type == 'version-update:semver-patch'}}
run: gh pr merge --auto --squash "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
```

> Kudos to GitHub 🍻 for providing these workflows! The [official guide is here][1]

But after this setup I still had the need for releasing patch releases by hand,
my task list was:

1. Pull latest contents from GitHub
2. Update tag in `Cargo.toml`
3. Create tag using `git tag`
4. Push to `main` using such tag
5. GitHub Workflow ran to publish to crates.io

But that workflow had many issues, repetition af manual tasks are error prone,
sometimes I had to cancel publish work because a mistaken version number was
bumped, or because I forgot to update `Cargo.toml`.

So I had to provide one more workflow, one that automates my task list.

## Preparing a Workflow to Bump Version and Push

I was inspired by NPM's `version` command, it basically covered my needs, when
you run `npm version <major | minor | patch>`, you have the `version` field
from `package.json` updated with a `+1` on the desired version digit, you also
get `git tag` run with the new version, thats great!

With that in mind, and as an acceptance criteria for my workflow, I started to
investigate on GitHub, looking for a `cargo` plugin that covers this for me.

And thats how I found `cargo-set-version` which was part of [`cargo-edit`][2] crate.

So the set of tasks to perform before publishing are:

1. Check crate integrity with a _dry run_ on `cargo publish`
2. Setup `cargo-set-version` as part of the CI Workflow (This involves setting up Rust)
3. Update `Cargo.toml` version
4. Commit changes to upstream pushing with tags included
5. Login to `Crates.io`
6. Upload the crate
7. Create GitHub Release

Let me walk you through the proccess.

1. Create a `Release` workflow on `.github/workflow/release.yml`

2. Provide workflow dispatch support with an input for version number to bump

```yml
name: Release

on:
workflow_dispatch:
inputs:
version:
type: choice
required: true
description: 'Version number to bump'
options:
- patch
- minor
- major
```

3. As we need to commit and push, permissions to write must be granted:

```yml
permissions:
contents: write
issues: write
pull-requests: write
```

4. Now lets implement jobs, the first job (`publish-dry-run`) will check for
crate integrity, the second (`release`) will perform release work:

```yml
jobs:
publish-dry-run:
name: "Runs cargo publish --dry-run"
runs-on: ubuntu-latest
steps:
- name: Checkout Source Code
uses: actions/checkout@v1

- name: Setup Rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable

- name: Check crate publish
run: cargo publish --dry-run
```

Then `release` job would look like this:

```yml
release:
name: Create Release
needs: publish-dry-run
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Setup Rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true

- name: Install `cargo-edit`
run: cargo install cargo-edit

- id: cargo-set-version
name: Set Version
run: cargo set-version --bump ${{ inputs.version }}

- name: Set Crate Version as Environment Variable
run: |
CARGO_TOML_VERSION=$(awk -F ' = ' '$1 ~ /version/ { gsub(/[\"]/, "", $2); printf("%s",$2) }' Cargo.toml)
echo "CRATE_VERSION=$CARGO_TOML_VERSION" >> $GITHUB_ENV

- name: Create Commit
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
git add .
git commit -m "chore: bump version to v$CRATE_VERSION"
git push origin main --follow-tags

- name: Login to Crates.io
run: cargo login ${CRATES_IO_TOKEN}
env:
CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}

- name: Publish crate
run: cargo publish

- name: Create Release
id: create_release
uses: actions/github-script@v5
with:
script: |
await github.request(`POST /repos/${{ github.repository }}/releases`, {
tag_name: "v${{ env.CRATE_VERSION }}",
generate_release_notes: true
});
```

I have been using this workflow since July 2023 and it works pretty nice!
You can [see how it performs here][3].

[Here][4] is the full source if you get lost!

## Conclusion

Automating repetitive tasks will always allow you to focus on more interesting
ones and at the same time will reduce errors dramatically. Releasing new
versions should be easy to achieve, even if you are AFK you should be able to
merge PRs and release versions of your packages, you never know who may need
a patch on one of your crates urgently!

[1]: https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions
[2]: https://crates.io/crates/cargo-edit
[3]: https://github.com/whizzes/pxid/actions/workflows/release.yml
[4]: https://github.com/whizzes/pxid/blob/e5125c8eaed7492a5c7143e9e61e9ab818d31a00/.github/workflows/release.yml#L1
65 changes: 58 additions & 7 deletions static/db/notes_index.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,22 @@
[
{
"meta": {
"title": "Rust GitHub Actions Release Workflow",
"description": "Setting up a GitHub Actions Workflow to tag and release a Rust Crate via Workflow Dispatch",
"icon": "rust",
"date": "2023-11-04",
"preview_image_url": "https://unsplash.com/photos/8gr6bObQLOI/download?ixid=M3wxMjA3fDB8MXxzZWFyY2h8NjB8fGF1dG9tYXRpb258ZW58MHx8fHwxNjk5MTI3MTI3fDA&force=true&w=640",
"published": true,
"categories": [
"rust",
"github",
"cd",
"ci",
"automation"
]
},
"slug": "rust-github-actions-release-workflow"
},
{
"meta": {
"title": "Desarrollando un servidor HTTP en Rust",
Expand All @@ -7,7 +25,12 @@
"date": "2023-09-12",
"preview_image_url": "https://images.unsplash.com/photo-1615834751896-b15e2330b289?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1734&q=1",
"published": true,
"categories": ["rust", "axum", "http", "rest"]
"categories": [
"rust",
"axum",
"http",
"rest"
]
},
"slug": "http-server-with-rust"
},
Expand Down Expand Up @@ -38,7 +61,12 @@
"date": "2023-08-27",
"preview_image_url": "https://images.unsplash.com/photo-1675654567731-6202cd552e3b?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&dl=ava-sol-zc6GiHMiQTE-unsplash.jpg",
"published": true,
"categories": ["rust", "http", "web", "ai"]
"categories": [
"rust",
"http",
"web",
"ai"
]
},
"slug": "rust-and-its-general-purpose-nature"
},
Expand All @@ -50,7 +78,14 @@
"date": "2023-07-22",
"preview_image_url": "https://images.unsplash.com/photo-1544383835-bda2bc66a55d?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2272&q=1",
"published": true,
"categories": ["id", "api", "graphql", "design", "software", "rust"]
"categories": [
"id",
"api",
"graphql",
"design",
"software",
"rust"
]
},
"slug": "pxid-developer-friendly-ids"
},
Expand All @@ -62,7 +97,12 @@
"date": "2023-06-25",
"preview_image_url": "https://images.unsplash.com/photo-1596496181848-3091d4878b24?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80",
"published": true,
"categories": ["rust", "axum", "graphql", "testing"]
"categories": [
"rust",
"axum",
"graphql",
"testing"
]
},
"slug": "writing-e2e-tests-for-axum-and-graphql"
},
Expand Down Expand Up @@ -113,7 +153,13 @@
"date": "2022-03-29",
"preview_image_url": "https://images.unsplash.com/photo-1544396821-4dd40b938ad3?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=2073&q=80",
"published": true,
"categories": ["svelte", "urql", "graphql", "file", "upload"]
"categories": [
"svelte",
"urql",
"graphql",
"file",
"upload"
]
},
"slug": "uploading-files-to-graphql-server-with-urql"
},
Expand Down Expand Up @@ -185,8 +231,13 @@
"date": "2021-09-13",
"preview_image_url": "https://images.unsplash.com/photo-1609131926660-3bec9d472529?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=2070&q=80",
"published": true,
"categories": ["rust", "tutorial", "windows", "install"]
"categories": [
"rust",
"tutorial",
"windows",
"install"
]
},
"slug": "installing-the-rust-programming-language-on-windows"
}
]
]

0 comments on commit be75c49

Please sign in to comment.