From ab3b4a7f3f9f73e04f768db7704675651a5aa143 Mon Sep 17 00:00:00 2001 From: Jon Seager Date: Wed, 29 Nov 2023 18:05:13 +0000 Subject: [PATCH 1/6] init: initial setup for shared workflows --- README.md | 25 ++++++ call-for-testing/README.md | 75 ++++++++++++++++++ call-for-testing/action.yaml | 113 +++++++++++++++++++++++++++ call-for-testing/template.md | 44 +++++++++++ get-architectures/README.md | 34 ++++++++ get-architectures/action.yaml | 39 ++++++++++ get-screenshots/README.md | 44 +++++++++++ get-screenshots/action.yaml | 130 +++++++++++++++++++++++++++++++ promote-to-stable/README.md | 33 ++++++++ promote-to-stable/action.yaml | 104 +++++++++++++++++++++++++ release-to-candidate/README.md | 38 +++++++++ release-to-candidate/action.yaml | 97 +++++++++++++++++++++++ sync-version/README.md | 38 +++++++++ sync-version/action.yaml | 46 +++++++++++ test-snap-build/README.md | 28 +++++++ test-snap-build/action.yaml | 22 ++++++ 16 files changed, 910 insertions(+) create mode 100644 README.md create mode 100644 call-for-testing/README.md create mode 100644 call-for-testing/action.yaml create mode 100644 call-for-testing/template.md create mode 100644 get-architectures/README.md create mode 100644 get-architectures/action.yaml create mode 100644 get-screenshots/README.md create mode 100644 get-screenshots/action.yaml create mode 100644 promote-to-stable/README.md create mode 100644 promote-to-stable/action.yaml create mode 100644 release-to-candidate/README.md create mode 100644 release-to-candidate/action.yaml create mode 100644 sync-version/README.md create mode 100644 sync-version/action.yaml create mode 100644 test-snap-build/README.md create mode 100644 test-snap-build/action.yaml diff --git a/README.md b/README.md new file mode 100644 index 0000000..f72c539 --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# Snapcrafters CI + +This repository contains common actions and tools used throughout the Snapcrafters +[organisation](https://github.com/snapcrafters) for the testing and delivery of our snaps. + +## Snapcrafters Actions + +The actions in this repo are all used during the build, test and release of our snaps. Each of them +listed below has it's own README + +- [snapcrafters/ci/call-for-testing](call-for-testing/README.md) +- [snapcrafters/ci/get-architectures](get-architectures/README.md) +- [snapcrafters/ci/get-screenshots](get-screenshots/README.md) +- [snapcrafters/ci/promote-to-stable](promote-to-stable/README.md) +- [snapcrafters/ci/release-to-candidate](release-to-candidate/README.md) +- [snapcrafters/ci/sync-version](sync-version/README.md) +- [snapcrafters/ci/test-snap-build](test-snap-build/README.md) + +### Usage + +You can see examples of these actions in use in the following repos: + +- [signal-desktop](https://github.com/snapcrafters/signal-desktop/main/.github/workflows) +- [mattermost-desktop](https://github.com/snapcrafters/mattermost-desktop/main/.github/workflows) +- [discord](https://github.com/snapcrafters/discord/main/.github/workflows) diff --git a/call-for-testing/README.md b/call-for-testing/README.md new file mode 100644 index 0000000..18afb6c --- /dev/null +++ b/call-for-testing/README.md @@ -0,0 +1,75 @@ +# snapcrafters/ci/call-for-testing + +Automatically creates a templated call for testing as a Github issue, containing the details of +newly released revisions and instructions on how to test and promote them. + +## Usage + +### Use in combination with `snapcrafters/ci/release-to-candidate` + +In this mode, the action will look for an artifact uploaded by the +`snapcrafters/ci/release-to-candidate` action that contains a manifest detailing the exact +revisions that were uploaded, and use those to populate the call for testing template. + +```yaml +jobs: + release: + name: ๐Ÿšข Release to latest/candidate + runs-on: ubuntu-latest + steps: + - name: ๐Ÿšข Release to latest/candidate + uses: snapcrafters/ci/release-to-candidate@main + with: + architecture: arm64 + launchpad-token: ${{ secrets.LAUNCHPAD_TOKEN }} + store-token: ${{ secrets.STORE_TOKEN }} + + call-for-testing: + name: ๐Ÿ“ฃ Create call for testing + needs: release + runs-on: ubuntu-latest + steps: + - name: ๐Ÿ“ฃ Create call for testing + uses: snapcrafters/ci/call-for-testing@main + with: + architectures: "amd64 arm64" + github-token: ${{ secrets.GITHUB_TOKEN }} +``` + +### Use standalone - `store-token` required + +In this mode, the action will use the store token to fetch the latest revision on the specified +channel (`candidate` by default) for each architecture and populate the call for testing with those +revisions. + +```yaml +jobs: + call-for-testing: + name: ๐Ÿ“ฃ Create call for testing + runs-on: ubuntu-latest + steps: + - name: ๐Ÿ“ฃ Create call for testing + uses: snapcrafters/ci/call-for-testing@main + with: + architectures: "amd64 arm64" + github-token: ${{ secrets.GITHUB_TOKEN }} + store-token: ${{ secrets.STORE_TOKEN }} +``` + +## API + +### Inputs + +| Key | Description | Required | Default | +| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------: | :---------------- | +| `architectures` | The architectures that the snap supports. | Y | `amd64 arm64` | +| `ci-repo` | The repo to fetch tools/templates from. Only for debugging. | N | `snapcrafters/ci` | +| `channel` | The channel to create the call for testing for. | N | `candidate` | +| `github-token` | A token with permissions to create issues on the repository. | Y | | +| `store-token` | A token with permissions to query the specified channel in the Snap Store. Only required if the revisions to test are not passed to the workflow by the `release-to-candidate` workflow | N | | + +### Outputs + +| Key | Description | Example | +| -------------- | ------------------------------------------------- | ------- | +| `issue-number` | The issue number containing the call for testing. | `12` | diff --git a/call-for-testing/action.yaml b/call-for-testing/action.yaml new file mode 100644 index 0000000..3b0cc40 --- /dev/null +++ b/call-for-testing/action.yaml @@ -0,0 +1,113 @@ +name: Create call for testing +description: Create a call for testing for a given snap repository +author: Snapcrafters +branding: + icon: message-circle + color: orange + +inputs: + architectures: + description: "The architectures to build the snap for" + required: true + ci-repo: + description: "The repo to fetch tools/templates from. Only for debugging" + default: "snapcrafters/ci" + required: false + channel: + description: "The channel to publish the snap to" + default: "candidate" + required: false + github-token: + description: "A token with permissions to create issues on the repository" + required: true + store-token: + description: "A token with permissions to upload to the specified channel" + required: false + +outputs: + issue-number: + description: "The issue number containing the call for testing" + value: ${{ steps.issue.outputs.number }} + +runs: + using: composite + steps: + - name: Checkout the source + uses: actions/checkout@v4 + + - name: Download manifest files + continue-on-error: true + uses: actions/download-artifact@v3 + with: + name: "manifests" + + - name: Setup snapcraft + shell: bash + run: | + sudo snap install snapcraft --classic + + - name: Write the arch/rev table + shell: bash + id: build + env: + SNAPCRAFT_STORE_CREDENTIALS: ${{ inputs.store-token }} + SNAP_NAME: ${{ github.event.repository.name }} + run: | + revisions=() + + # Build the initial structure for the HTML table including the header row. + table="" + + # If we were able to fetch build manifests from previous step, use those + if ls -l manifest-*.yaml &>/dev/null; then + echo "Found build manifests - populating template with revisions from the manifests" + + # Iterate over the manifest files and write the table rows for each architecture + for file in $(ls manifest-*.yaml); do + # Parse the arch and the revision + arch="$(cat "${file}" | yq -r '.architecture')" + rev="$(cat "${file}" | yq -r '.revision')" + # Write the table row and add the revision to the list we're tracking + table="${table}" + revisions+=("$rev") + done + else + echo "No build manifests found - populating template with information from the store" + + # Otherwise, get the latest revision for each architecture in the release channel + for arch in ${{ inputs.architectures }}; do + rev="$(snapcraft list-revisions "${SNAP_NAME}" --arch "$arch" | grep "latest/${{ inputs.channel }}*" | head -n1 | cut -d' ' -f1)" + revisions+=("$rev") + # Add a row to the HTML table + table="${table}" + done + fi + + # Add the closing tags for the table + table="${table}
CPU ArchitectureRevision
${arch}${rev}
${arch}${rev}
" + + # Get a comma separated list of revisions + printf -v joined '%s,' "${revisions[@]}" + + version="$(cat snap/snapcraft.yaml | yq -r '.version')" + echo "version=${version}" >> "$GITHUB_OUTPUT" + echo "revisions=${joined%,}" >> "$GITHUB_OUTPUT" + echo "table=${table}" >> "$GITHUB_OUTPUT" + + - name: Fetch the call for testing template + shell: bash + run: | + wget -qO template.md "https://raw.githubusercontent.com/${{ inputs.ci-repo }}/main/call-for-testing/template.md" + + - name: Create call for testing issue + uses: JasonEtco/create-an-issue@v2 + id: issue + env: + GITHUB_TOKEN: ${{ inputs.github-token }} + snap_name: ${{ github.event.repository.name }} + channel: ${{ inputs.channel }} + revisions: ${{ steps.build.outputs.revisions }} + table: ${{ steps.build.outputs.table }} + version: ${{ steps.build.outputs.version }} + with: + filename: ./template.md diff --git a/call-for-testing/template.md b/call-for-testing/template.md new file mode 100644 index 0000000..2500af7 --- /dev/null +++ b/call-for-testing/template.md @@ -0,0 +1,44 @@ +--- +title: Call for testing `{{ env.snap_name }}` +labels: testing +--- + +A new version ({{ env.version }}) of `{{ env.snap_name }}` was just pushed to the `{{ env.channel }}` channel [in the snap store](https://snapcraft.io/{{ env.snap_name }}). The following revisions are available. + +{{ env.table }} + +## Automated screenshots + +The snap will be installed in a VM automatically; screenshots will be posted as a comment on this issue shortly. + +## How to test it manually + +1. Stop the application if it was already running +1. Upgrade to this version by running + + ```shell + snap refresh {{ env.snap_name }} --{{ env.channel }} + ``` + +1. Start the app and test it out. +1. Finally, add a comment below explaining whether this app is working, and **include the output of the following command**. + + ```shell + snap version; lscpu | grep Architecture; snap info {{ env.snap_name }} | grep installed + ``` + +## How to release it + +Maintainers can promote this to stable by commenting `/promote [,] stable [done]`. + +> For example +> +> - To promote a single revision, run `/promote stable` +> - To promote multiple revisions, run `/promote , stable` +> - To promote a revision and close the issue, run `/promote , stable done` + +You can promote all revisions that were just built with: + +``` +/promote {{ env.revisions }} stable done +``` diff --git a/get-architectures/README.md b/get-architectures/README.md new file mode 100644 index 0000000..e0bdab7 --- /dev/null +++ b/get-architectures/README.md @@ -0,0 +1,34 @@ +# snapcrafters/ci/get-architectures + +Parses a `snapcraft.yaml` and returns the list of architectures supported both as a JSON array, and +a space-separated string. + +## Usage + +```yaml +# ... +jobs: + get-architectures: + name: ๐Ÿ–ฅ Get snap architectures + runs-on: ubuntu-latest + outputs: + architectures: ${{ steps.get-architectures.outputs.architectures }} + architectures-list: ${{ steps.get-architectures.outputs.architectures-list }} + steps: + - name: ๐Ÿ–ฅ Get snap architectures + id: get-architectures + uses: snapcrafters/ci/get-architectures@main +``` + +## API + +### Inputs + +None + +### Outputs + +| Key | Description | Example | +| -------------------- | -------------------------------------------------------------- | ------------------- | +| `architectures` | A space-separated list of architectures supported by the snap. | `amd64 arm64` | +| `architectures-list` | A JSON list of architectures supported by the snap | `["amd64" "arm64"]` | diff --git a/get-architectures/action.yaml b/get-architectures/action.yaml new file mode 100644 index 0000000..7a121e4 --- /dev/null +++ b/get-architectures/action.yaml @@ -0,0 +1,39 @@ +name: Get Architectures +description: Get the architectures supported by a given snap +author: Snapcrafters +branding: + icon: code + color: orange + +outputs: + architectures: + description: "A space-separated list of architectures supported by the snap" + value: ${{ steps.architectures.outputs.architectures }} + architectures-list: + description: "A JSON list of architectures supported by the snap" + value: ${{ steps.architectures.outputs.architectures_list }} + +runs: + using: composite + steps: + - name: Checkout the source + uses: actions/checkout@v4 + + - name: Compute architectures + id: architectures + shell: bash + run: | + # Get the list as a json array. E.g. ["amd64", "arm64"] + architectures_list="$(cat snap/snapcraft.yaml | yq -r -I=0 -o=json '[.architectures[]]')" + + # Get the list as a space-separated string. E.g. "amd64" "arm64" + architectures="$(cat snap/snapcraft.yaml | yq -r -I=0 -o=csv '[.architectures[]]' | tr ',' ' ')" + + # Handle the case where architectures is a list of objects + if echo "$architectures" | grep -q "build-on"; then + architectures_list="$(cat snap/snapcraft.yaml | yq -r -I=0 -o=json '[.architectures[]."build-on"]')" + architectures="$(cat snap/snapcraft.yaml | yq -r -I=0 -o=csv '[.architectures[]."build-on"]' | tr ',' ' ')" + fi + + echo "architectures_list=$architectures_list" >> "$GITHUB_OUTPUT" + echo "architectures=$architectures" >> "$GITHUB_OUTPUT" diff --git a/get-screenshots/README.md b/get-screenshots/README.md new file mode 100644 index 0000000..9c2a3a3 --- /dev/null +++ b/get-screenshots/README.md @@ -0,0 +1,44 @@ +# snapcrafters/ci/get-screenshots + +Deploys the snap from `latest/candidate` in a LXD desktop VM, then takes screenshots of the whole +desktop, and the most recent active window after the snap was launched. Screenshots are then +committed to [ci-screenshots](https://github.com/snapcrafters/ci-screenshots), and added to a comment on +the original call for testing issue. + +## Usage + +```yaml +# ... +jobs: + screenshots: + name: ๐Ÿ“ธ Gather screenshots + needs: call-for-testing + runs-on: ubuntu-latest + steps: + - name: ๐Ÿ“ธ Gather screenshots + uses: snapcrafters/ci/get-screenshots@main + with: + issue-number: ${{ needs.call-for-testing.outputs.issue-number }} + github-token: ${{ secrets.GITHUB_TOKEN }} + screenshots-token: ${{ secrets.SCREENSHOT_COMMIT_TOKEN }} +``` + +## API + +### Inputs + +| Key | Description | Required | Default | +| ------------------- | ------------------------------------------------------------------------------------------------------------------ | :------: | :---------------------------- | +| `issue-number` | The issue number to post the screenshots to. | Y | | +| `ci-repo` | The repo to fetch tools/templates from. Only for debugging. | N | `snapcrafters/ci` | +| `channel` | The channel to create the call for testing for. | N | `candidate` | +| `github-token` | A token with permissions to common on issues in the repository. | Y | | +| `screenshots-repo` | The repository where screenshots should be uploaded. | N | `snapcrafters/ci-screenshots` | +| `screesnhots-token` | A token with permissions to commit screenshots to [ci-screenshots](https://github.com/snapcrafters/ci-screenshots) | Y | | + +### Outputs + +| Key | Description | Example | +| -------- | ------------------------------------------------------------- | ------- | +| `screen` | A URL pointing to a screenshot of the whole screen in the VM | `12` | +| `window` | A URL pointing to a screenshot of the active window in the VM | `12` | diff --git a/get-screenshots/action.yaml b/get-screenshots/action.yaml new file mode 100644 index 0000000..c543e10 --- /dev/null +++ b/get-screenshots/action.yaml @@ -0,0 +1,130 @@ +name: Get screenshots +description: Install a snap in a VM and get screenshots of it running +author: Snapcrafters +branding: + icon: camera + color: orange + +inputs: + issue-number: + description: "The issue number to post the screenshots into" + required: true + ci-repo: + description: "The repo to fetch tools/templates from. Only for debugging." + default: "snapcrafters/ci" + required: false + channel: + description: "The channel to publish the snap to" + default: "candidate" + required: false + github-token: + description: "A token with permissions to comment on issues" + required: true + screenshots-repo: + description: "The repository where screenshots should be uploaded." + default: "snapcrafters/ci-screenshots" + required: false + screenshots-token: + description: "A token with permissions to commit files to the screenshots repo" + required: true + +outputs: + screen: + description: "URL to a screenshot of the full screen of the VM" + value: ${{ steps.screenshots.outputs.screen }} + window: + description: "URL to a screenshot of the full screen of the VM" + value: ${{ steps.screenshots.outputs.window }} + +runs: + using: composite + steps: + - name: Checkout the code + uses: actions/checkout@v4 + + - name: Setup ghvmctl + uses: jnsgruk/ghvmctl/setup-ghvmctl@candidate + + - name: Download manifest files (if available) + continue-on-error: true + uses: actions/download-artifact@v3 + with: + name: manifests + + - name: Prepare VM + shell: bash + env: + SNAP_NAME: ${{ github.event.repository.name }} + run: | + ghvmctl prepare + + # If we got a manifest file then parse the revision from it + if ls manifest-amd64.yaml &>/dev/null; then + rev="$(cat manifest-amd64.yaml | yq -r '.revision')" + echo "Installing snap revision '${rev}' from build manifest" + ghvmctl install-snap "${SNAP_NAME}" --revision "${rev}" + else + echo "Installing snap from 'latest/${{ inputs.channel}}'" + ghvmctl install-snap "${SNAP_NAME}" --channel "latest/${{ inputs.channel }}" + fi + + ghvmctl run-snap "${SNAP_NAME}" + sleep 60 + + - name: Gather screenshots + shell: bash + run: | + ghvmctl screenshot-full + ghvmctl screenshot-window + + - name: Output application logs + shell: bash + env: + SNAP_NAME: ${{ github.event.repository.name }} + run: | + ghvmctl exec "cat /home/ubuntu/${SNAP_NAME}.log" + + - name: Checkout screenshots repo + uses: actions/checkout@v4 + with: + repository: ${{ inputs.screenshots-repo }} + path: ci-screenshots + token: ${{ inputs.screenshots-token }} + + - name: Upload screenshots to screenshots repo + shell: bash + id: screenshots + env: + SNAP_NAME: ${{ github.event.repository.name }} + run: | + file_prefix="$(date +%Y%m%d)-${SNAP_NAME}-${{ inputs.issue-number }}" + + pushd ci-screenshots + mv "$HOME/ghvmctl-screenshots/screenshot-screen.png" "${file_prefix}-screen.png" + mv "$HOME/ghvmctl-screenshots/screenshot-window.png" "${file_prefix}-window.png" + + git config --global user.email "merlijn.sebrechts+snapcrafters-bot@gmail.com" + git config --global user.name "Snapcrafters Bot" + + git add -A . + git commit -m "data: screenshots for snapcrafters/${SNAP_NAME}#${{ inputs.issue-number }}" + git push origin main + + echo "screen=https://raw.githubusercontent.com/${{ inputs.screenshots-repo }}/main/${file_prefix}-screen.png" >> "$GITHUB_OUTPUT" + echo "window=https://raw.githubusercontent.com/${{ inputs.screenshots-repo }}/main/${file_prefix}-window.png" >> "$GITHUB_OUTPUT" + + - name: Comment on call for testing issue with screenshots + uses: actions/github-script@v7 + with: + script: | + github.rest.issues.createComment({ + issue_number: ${{ inputs.issue-number }}, + owner: context.repo.owner, + repo: context.repo.repo, + body: `The following screenshots were taken during automated testing: + + ![window](${{ steps.screenshots.outputs.window }}) + + ![screen](${{ steps.screenshots.outputs.screen }}) + ` + }) diff --git a/promote-to-stable/README.md b/promote-to-stable/README.md new file mode 100644 index 0000000..81b4e15 --- /dev/null +++ b/promote-to-stable/README.md @@ -0,0 +1,33 @@ +# snapcrafters/ci/promote-to-stable + +Promote to stable is generally triggered in response to a Snapcrafters reviewer posting a comment +containing a `/promote` command. Once the arguments are successfully parsed, the specified +revisions are promoted to `latest/stable` + +## Usage + +```yaml +# ... +jobs: + promote: + name: โฌ†๏ธ Promote to stable + runs-on: ubuntu-latest + steps: + - name: โฌ†๏ธ Promote to stable + uses: snapcrafters/ci/promote-to-stable@main + with: + store-token: ${{ secrets.SNAP_STORE_STABLE }} +``` + +## API + +### Inputs + +| Key | Description | Required | Default | +| -------------- | ---------------------------------------------------------------------------------------- | :------: | :------ | +| `github-token` | A token with permissions to write issues on the repository | Y | | +| `store-token` | A token with permissions to upload and release to the `stable` channel in the Snap Store | Y | | + +### Outputs + +None diff --git a/promote-to-stable/action.yaml b/promote-to-stable/action.yaml new file mode 100644 index 0000000..acd5491 --- /dev/null +++ b/promote-to-stable/action.yaml @@ -0,0 +1,104 @@ +name: Promote to latest/stable +description: Promotes a given set of revisions from candidate -> stable +author: Snapcrafters +branding: + icon: trending-up + color: orange + +inputs: + github-token: + description: "A token with permissions to write issues on the repository" + required: true + store-token: + description: "A token with permissions to upload to the specified channel" + required: true + +runs: + using: composite + steps: + - name: Parse slash command + id: command + uses: xt0rted/slash-command-action@v2 + with: + repo-token: ${{ inputs.github-token }} + command: promote + reaction: "true" + reaction-type: "eyes" + allow-edits: "false" + permission-level: write + + - name: Install snapcraft + shell: bash + run: | + sudo snap install --classic snapcraft + + - name: Promote snap to latest/stable + id: promote + env: + SNAPCRAFT_STORE_CREDENTIALS: ${{ inputs.store-token }} + SNAP_NAME: ${{ github.event.repository.name }} + shell: bash + run: | + echo "The command was '${{ steps.command.outputs.command-name }}' with arguments '${{ steps.command.outputs.command-arguments }}'" + + arguments=(${{ steps.command.outputs.command-arguments }}) + revision=${arguments[0]} + channel=${arguments[1]} + done=${arguments[2]} + + re='^[0-9]+([,][0-9]+)*$' + if [[ ! "$revision" =~ $re ]]; then + echo "revision must be a number or a comma separated list of numbers, not '$revision'!" + exit 1 + fi + + if [[ "$channel" != "stable" ]]; then + echo "I can only promote to stable, not '$channel'!" + exit 1 + fi + + if [[ -n "$done" && "$done" != "done" ]]; then + echo "The third argument should be 'done' or empty" + exit 1 + fi + + # Iterate over each specified revision and release + revs=$(echo $revision | tr "," "\n") + released_revs=() + + for r in $revs; do + snapcraft release $SNAP_NAME "$r" "$channel" + released_revs+=("$r") + done + + # Get a comma separated list of released revisions + printf -v joined '%s,' "${released_revs[@]}" + + echo "revisions=${joined%,}" >> $GITHUB_OUTPUT + echo "channel=$channel" >> $GITHUB_OUTPUT + echo "done=$done" >> $GITHUB_OUTPUT + + - name: Comment on call for testing issue + uses: actions/github-script@v7 + with: + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: 'The following revisions were released to the `${{ steps.promote.outputs.channel }}` channel: `${{ steps.promote.outputs.revisions }}`' + }) + + - name: Close call for testing issue + if: ${{ steps.promote.outputs.done }} == "done" + uses: actions/github-script@v7 + with: + script: | + if ("${{ steps.promote.outputs.done }}" === "done") { + github.rest.issues.update({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + state: 'closed' + }) + } diff --git a/release-to-candidate/README.md b/release-to-candidate/README.md new file mode 100644 index 0000000..472c88b --- /dev/null +++ b/release-to-candidate/README.md @@ -0,0 +1,38 @@ +# snapcrafters/ci/release-to-candidate + +This action is used to run `snapcraft remote-build` for a given Snap, and a given architecture. +Following that, the snap is released to the `latest/candidate` automatically. + +## Usage + +```yaml +# ... +jobs: + release: + name: ๐Ÿšข Release to latest/candidate + runs-on: ubuntu-latest + steps: + - name: ๐Ÿšข Release to latest/candidate + uses: snapcrafters/ci/release-to-candidate@main + with: + architecture: arm64 + launchpad-token: ${{ secrets.LAUNCHPAD_TOKEN }} + store-token: ${{ secrets.STORE_TOKEN }} +``` + +## API + +### Inputs + +| Key | Description | Required | Default | +| ----------------- | ----------------------------------------------------------------------------------------- | :------: | :---------- | +| `architecture` | The architecture for which to build the snap. | Y | `amd64` | +| `channel` | The channel to release the snap to. | N | `candidate` | +| `launchpad-token` | A token with permissions to create Launchpad remote builds. | Y | | +| `store-token` | A token with permissions to upload and release to the specified channel in the Snap Store | Y | | + +### Outputs + +| Key | Description | Example | +| ---------- | ----------------------------------------- | ------- | +| `revision` | The Snap Store revision that was created. | `15` | diff --git a/release-to-candidate/action.yaml b/release-to-candidate/action.yaml new file mode 100644 index 0000000..376f715 --- /dev/null +++ b/release-to-candidate/action.yaml @@ -0,0 +1,97 @@ +name: Release to Candidate +description: Builds a snap using `snapcraft remote-build` and releases it to candidate +author: Snapcrafters +branding: + icon: tag + color: orange + +inputs: + architecture: + description: "The architecture to build the snap for" + default: amd64 + required: true + channel: + description: "The channel to publish the snap to" + default: "candidate" + required: false + launchpad-token: + description: "A token with permissions to create remote builds on Launchpad" + required: true + store-token: + description: "A token with permissions to upload to the specified channel" + required: true + +outputs: + revision: + description: "The revision of the uploaded snap" + value: ${{ steps.publish.outputs.revision }} + +runs: + using: composite + steps: + - name: Checkout the source + uses: actions/checkout@v4 + + - name: Setup snapcraft + shell: bash + run: | + sudo snap install snapcraft --classic + + # Setup Launchpad credentials + mkdir -p ~/.local/share/snapcraft/provider/launchpad + echo "${{ inputs.launchpad-token }}" > ~/.local/share/snapcraft/provider/launchpad/credentials + git config --global user.email "github-actions@github.com" + git config --global user.name "Github Actions" + + # Install moreutils so we have access to sponge + sudo apt-get update; sudo apt-get install -y moreutils + + - name: Build the snap (${{ inputs.architecture }}) + id: build + shell: bash + env: + name: ${{ github.event.repository.name }} + arch: ${{ inputs.architecture }} + run : | + # Remove the architecture definition from the snapcraft.yaml due to: + # https://bugs.launchpad.net/snapcraft/+bug/1885150 + cat snap/snapcraft.yaml | yq 'del(.architectures)' | sponge snap/snapcraft.yaml + + snapcraft remote-build --launchpad-accept-public-upload --build-for="${arch}" + + version="$(cat snap/snapcraft.yaml | yq -r '.version')" + echo "snap=${name}_${version}_${arch}.snap" >> "$GITHUB_OUTPUT" + echo "classic=false" >> "$GITHUB_OUTPUT" + + if [[ "$(cat snap/snapcraft.yaml | yq -r '.confinement')" == "classic" ]]; then + echo "classic=true" >> "$GITHUB_OUTPUT" + fi + + # Write the manifest file which is used by later steps + echo "name: ${name}" >> "manifest-${arch}.yaml" + echo "architecture: ${arch}" >> "manifest-${arch}.yaml" + + - name: Review the built snap + uses: diddlesnaps/snapcraft-review-action@v1 + with: + snap: ${{ steps.build.outputs.snap }} + isClassic: ${{ steps.build.outputs.classic }} + + - name: Release the built snap to latest/${{ inputs.channel }} + id: publish + shell: bash + env: + SNAPCRAFT_STORE_CREDENTIALS: ${{ inputs.store-token }} + SNAP_FILE: ${{ steps.build.outputs.snap }} + run: | + snapcraft_out="$(snapcraft push "$SNAP_FILE" --release="${{ inputs.channel }}")" + revision="$(echo "$snapcraft_out" | grep -Po "Revision \K[^ ]+")" + echo "revision=${revision}" >> "$GITHUB_OUTPUT" + echo "revision: ${revision}" >> "manifest-${{ inputs.architecture }}.yaml" + + # Upload the manifest file as an artifact for retrieval during future actions + - name: Upload revision manifest + uses: actions/upload-artifact@v3.1.3 + with: + name: "manifests" + path: "manifest-${{ inputs.architecture }}.yaml" diff --git a/sync-version/README.md b/sync-version/README.md new file mode 100644 index 0000000..7a33c4a --- /dev/null +++ b/sync-version/README.md @@ -0,0 +1,38 @@ +# snapcrafters/ci/sync-version + +Takes an `update-script` input which should be a script that automatically checks for version +updates to the upstream application, and modifies the `snapcraft.yaml` as appropriate. This action +takes care of identifying and committing those changes. + +## Usage + +```yaml +jobs: + sync: + name: ๐Ÿ”„ Sync version with upstream + runs-on: ubuntu-latest + steps: + - name: ๐Ÿ”„ Sync version with upstream + uses: snapcrafters/ci/sync-version@main + with: + token: ${{ secrets.TOKEN }} + update-script: | + VERSION=$( + curl -sL https://api.github.com/repos/jnsgruk/gosherve/releases | + jq . | grep tag_name | grep -v beta | head -n 1 | cut -d'"' -f4 | tr -d 'v' + ) + sed -i 's/^\(version: \).*$/\1'"$VERSION"'/' snap/snapcraft.yaml +``` + +## API + +### Inputs + +| Key | Description | Required | +| --------------- | -------------------------------------------------------------------------------------------------- | :------: | +| `token` | A token with permissions to commit to the repository. | Y | +| `update-script` | A script that checks for version updates and updates `snapcraft.yaml` and other files if required. | Y | + +### Outputs + +None diff --git a/sync-version/action.yaml b/sync-version/action.yaml new file mode 100644 index 0000000..0a31b6a --- /dev/null +++ b/sync-version/action.yaml @@ -0,0 +1,46 @@ +name: Setup ghvmctl +description: Configure a runner for access to the KVM socket and install ghvmctl +author: Snapcrafters +branding: + icon: refresh-cw + color: orange + +inputs: + update-script: + description: "Bash script that fetches the latest version and updates the source tree as required." + required: true + token: + required: true + description: A token with write privileges to the repository. + +runs: + using: composite + steps: + - name: Checkout the source + uses: actions/checkout@v4 + with: + token: ${{ inputs.token }} + + - name: Run update script + shell: bash + run: | + ${{ inputs.update-script }} + + - name: Check for modified files + shell: bash + id: git-check + run: | + MODIFIED=$([ -z "`git status --porcelain`" ] && echo "false" || echo "true") + echo "modified=$MODIFIED" >> $GITHUB_OUTPUT + + - name: Commit changes + if: steps.git-check.outputs.modified == 'true' + shell: bash + env: + SNAP_NAME: ${{ github.event.repository.name }} + run: | + version="$(cat snap/snapcraft.yaml | yq -r '.version')" + git config --global user.name 'Snapcrafters Bot' + git config --global user.email 'merlijn.sebrechts+snapcrafters-bot@gmail.com' + git commit -am "chore: bump ${SNAP_NAME} to version $version" + git push diff --git a/test-snap-build/README.md b/test-snap-build/README.md new file mode 100644 index 0000000..c89ae02 --- /dev/null +++ b/test-snap-build/README.md @@ -0,0 +1,28 @@ +# snapcrafters/ci/test-snap-build + +This action tries to build the snap "locally" on the Github Actions runner for `amd64` only. Once +the snap is built, it is reviewed using [review-tools](https://snapcraft.io/review-tools). Designed +to be a quick "smoke test" that doesn't require any special credentials or secrets. + +## Usage + +```yaml +# ... +jobs: + build: + name: ๐Ÿงช Build snap on amd64 + runs-on: ubuntu-latest + steps: + - name: ๐Ÿงช Build snap on amd64 + uses: snapcrafters/ci/test-snap-build@main +``` + +## API + +### Inputs + +None + +### Outputs + +None diff --git a/test-snap-build/action.yaml b/test-snap-build/action.yaml new file mode 100644 index 0000000..66397e4 --- /dev/null +++ b/test-snap-build/action.yaml @@ -0,0 +1,22 @@ +name: Test Snap Build +description: Build the snap locally using Snapcraft and review the output. +author: Snapcrafters +branding: + icon: zap + color: orange + +runs: + using: composite + steps: + - name: Checkout the source + uses: actions/checkout@v4 + + - name: Build snap + uses: snapcore/action-build@v1 + id: build + + - name: Review snap + uses: diddlesnaps/snapcraft-review-action@v1 + with: + snap: ${{ steps.build.outputs.snap }} + isClassic: 'false' From 04c3512f5ba6f0356bcc0189a90d2e29cbc1c335 Mon Sep 17 00:00:00 2001 From: Jon Seager Date: Sat, 2 Dec 2023 10:57:15 +0000 Subject: [PATCH 2/6] docs: fix inconsistencies in the docs per review --- call-for-testing/README.md | 2 +- get-architectures/README.md | 8 ++++---- get-screenshots/README.md | 10 +++++----- release-to-candidate/README.md | 2 +- release-to-candidate/action.yaml | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/call-for-testing/README.md b/call-for-testing/README.md index 18afb6c..e82ec61 100644 --- a/call-for-testing/README.md +++ b/call-for-testing/README.md @@ -62,7 +62,7 @@ jobs: | Key | Description | Required | Default | | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------: | :---------------- | -| `architectures` | The architectures that the snap supports. | Y | `amd64 arm64` | +| `architectures` | The architectures that the snap supports. | Y | | | `ci-repo` | The repo to fetch tools/templates from. Only for debugging. | N | `snapcrafters/ci` | | `channel` | The channel to create the call for testing for. | N | `candidate` | | `github-token` | A token with permissions to create issues on the repository. | Y | | diff --git a/get-architectures/README.md b/get-architectures/README.md index e0bdab7..f38f14c 100644 --- a/get-architectures/README.md +++ b/get-architectures/README.md @@ -28,7 +28,7 @@ None ### Outputs -| Key | Description | Example | -| -------------------- | -------------------------------------------------------------- | ------------------- | -| `architectures` | A space-separated list of architectures supported by the snap. | `amd64 arm64` | -| `architectures-list` | A JSON list of architectures supported by the snap | `["amd64" "arm64"]` | +| Key | Description | Example | +| -------------------- | -------------------------------------------------------------- | -------------------- | +| `architectures` | A space-separated list of architectures supported by the snap. | `amd64 arm64` | +| `architectures-list` | A JSON list of architectures supported by the snap | `["amd64", "arm64"]` | diff --git a/get-screenshots/README.md b/get-screenshots/README.md index 9c2a3a3..edc91da 100644 --- a/get-screenshots/README.md +++ b/get-screenshots/README.md @@ -34,11 +34,11 @@ jobs: | `channel` | The channel to create the call for testing for. | N | `candidate` | | `github-token` | A token with permissions to common on issues in the repository. | Y | | | `screenshots-repo` | The repository where screenshots should be uploaded. | N | `snapcrafters/ci-screenshots` | -| `screesnhots-token` | A token with permissions to commit screenshots to [ci-screenshots](https://github.com/snapcrafters/ci-screenshots) | Y | | +| `screenshots-token` | A token with permissions to commit screenshots to [ci-screenshots](https://github.com/snapcrafters/ci-screenshots) | Y | | ### Outputs -| Key | Description | Example | -| -------- | ------------------------------------------------------------- | ------- | -| `screen` | A URL pointing to a screenshot of the whole screen in the VM | `12` | -| `window` | A URL pointing to a screenshot of the active window in the VM | `12` | +| Key | Description | +| -------- | ------------------------------------------------------------- | +| `screen` | A URL pointing to a screenshot of the whole screen in the VM | +| `window` | A URL pointing to a screenshot of the active window in the VM | diff --git a/release-to-candidate/README.md b/release-to-candidate/README.md index 472c88b..6bf00fb 100644 --- a/release-to-candidate/README.md +++ b/release-to-candidate/README.md @@ -26,7 +26,7 @@ jobs: | Key | Description | Required | Default | | ----------------- | ----------------------------------------------------------------------------------------- | :------: | :---------- | -| `architecture` | The architecture for which to build the snap. | Y | `amd64` | +| `architecture` | The architecture for which to build the snap. | N | `amd64` | | `channel` | The channel to release the snap to. | N | `candidate` | | `launchpad-token` | A token with permissions to create Launchpad remote builds. | Y | | | `store-token` | A token with permissions to upload and release to the specified channel in the Snap Store | Y | | diff --git a/release-to-candidate/action.yaml b/release-to-candidate/action.yaml index 376f715..91e4cf8 100644 --- a/release-to-candidate/action.yaml +++ b/release-to-candidate/action.yaml @@ -9,7 +9,7 @@ inputs: architecture: description: "The architecture to build the snap for" default: amd64 - required: true + required: false channel: description: "The channel to publish the snap to" default: "candidate" From 43d4bceebc486cab5b50e61fa2d817f0fde4c841 Mon Sep 17 00:00:00 2001 From: Jon Seager Date: Sat, 2 Dec 2023 10:58:30 +0000 Subject: [PATCH 3/6] feat: add the `setup-ghvmctl` action to this repository --- get-screenshots/action.yaml | 2 +- setup-ghvmctl/README.md | 48 +++++++++++++++++++++++++++++++++++++ setup-ghvmctl/action.yaml | 25 +++++++++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 setup-ghvmctl/README.md create mode 100644 setup-ghvmctl/action.yaml diff --git a/get-screenshots/action.yaml b/get-screenshots/action.yaml index c543e10..cb7add2 100644 --- a/get-screenshots/action.yaml +++ b/get-screenshots/action.yaml @@ -43,7 +43,7 @@ runs: uses: actions/checkout@v4 - name: Setup ghvmctl - uses: jnsgruk/ghvmctl/setup-ghvmctl@candidate + uses: snapcrafters/ci/setup-ghvmctl@main - name: Download manifest files (if available) continue-on-error: true diff --git a/setup-ghvmctl/README.md b/setup-ghvmctl/README.md new file mode 100644 index 0000000..c9980e2 --- /dev/null +++ b/setup-ghvmctl/README.md @@ -0,0 +1,48 @@ +# snapcrafters/ci/setup-ghvmctl + +A simple Github Action for configuring [ghvmctl](https://github.com/snapcrafters/ghvmctl) for use +on Github Actions runners is also included in this repository. It has three major functions: + +- Enable KVM on the runner +- Install and initialise LXD +- Install and configure `ghvmctl` + +## Usage + +```yaml +jobs: + test-snap: + runs-on: ubuntu-latest + steps: + - name: Setup ghvmctl + uses: snapcrafters/ci/setup-ghvmctl@main + + - name: Prepare test environment + run: | + # Prepare the VM, install and launch the app on the desktop + ghvmctl prepare + ghvmctl install-snap signal-desktop --channel candidate + ghvmctl run-snap signal-desktop + + - name: Take screenshots & output logs + run: | + ghvmctl screenshot-full + ghvmctl screenshot-window + ghvmctl exec "cat /home/ubuntu/signal-desktop.log" + + - name: Upload screenshots + uses: actions/upload-artifact@v3.1.3 + with: + name: "screenshots" + path: "~/ghvmctl-screenshots/*.png" +``` + +## API + +### Inputs + +None + +### Outputs + +None diff --git a/setup-ghvmctl/action.yaml b/setup-ghvmctl/action.yaml new file mode 100644 index 0000000..1bd7ecc --- /dev/null +++ b/setup-ghvmctl/action.yaml @@ -0,0 +1,25 @@ +name: Setup ghvmctl +description: Configure a runner for access to the KVM socket and install ghvmctl +author: snapcrafters +branding: + icon: refresh-cw + color: orange + +runs: + using: composite + steps: + - name: Enable KVM on the Github Actions runner + shell: bash + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: Setup LXD + uses: canonical/setup-lxd@v0.1.1 + + - name: Setup ghvmctl + shell: bash + run: | + sudo snap install ghvmctl + sudo snap connect ghvmctl:lxd lxd:lxd From 00c6584e92fa2bd099ca1b32949dbc73333c935b Mon Sep 17 00:00:00 2001 From: Jon Seager Date: Sat, 2 Dec 2023 10:59:22 +0000 Subject: [PATCH 4/6] feat: make the test-snap-build action aware of plugs/slots Also adds an input to optionally install the snap on the runner after the build/review process. --- test-snap-build/README.md | 27 +++++++++++++++++---- test-snap-build/action.yaml | 48 +++++++++++++++++++++++++++++++++++-- 2 files changed, 69 insertions(+), 6 deletions(-) diff --git a/test-snap-build/README.md b/test-snap-build/README.md index c89ae02..811d79a 100644 --- a/test-snap-build/README.md +++ b/test-snap-build/README.md @@ -1,8 +1,21 @@ # snapcrafters/ci/test-snap-build -This action tries to build the snap "locally" on the Github Actions runner for `amd64` only. Once -the snap is built, it is reviewed using [review-tools](https://snapcraft.io/review-tools). Designed -to be a quick "smoke test" that doesn't require any special credentials or secrets. +Designed to be a quick "smoke test" that doesn't require any special credentials or secrets. This +action tries to build the snap "locally" on the Github Actions runner for `amd64` only. Once the +snap is built, it is reviewed using [review-tools]. + +Information about your snap will be automatically parsed for the review stage. If you need to +specify plug or slot declarations per the [snapcraft-review-tools] README, you can include any of +the following files in your repository, which will be passed to the review action: + +- `slot-declaration.json` +- `.github/slot-declaration.json` +- `plug-declaration.json` +- `.github/plug-declaration.json` + +> [!NOTE] +> We don't use `remote-build` here, because that requires access to a Launchpad token. +> Exposing tokens in a PR build can be [dangerous] from a security standpoint. ## Usage @@ -21,8 +34,14 @@ jobs: ### Inputs -None +| Key | Description | Required | Default | +| --------- | -------------------------------------------------------------- | :------: | :------ | +| `install` | If `true`, the built snap is install on the runner after build | N | `false` | ### Outputs None + +[review-tools]: https://snapcraft.io/review-tools +[snapcraft-review-tools]: https://github.com/diddlesnaps/snapcraft-review-action/tree/master +[dangerous]: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ diff --git a/test-snap-build/action.yaml b/test-snap-build/action.yaml index 66397e4..f2ea01b 100644 --- a/test-snap-build/action.yaml +++ b/test-snap-build/action.yaml @@ -5,6 +5,12 @@ branding: icon: zap color: orange +inputs: + install: + description: "Option to install the snap on the runner after build" + default: "false" + required: false + runs: using: composite steps: @@ -15,8 +21,46 @@ runs: uses: snapcore/action-build@v1 id: build - - name: Review snap + - name: Parse snap review information + id: parse + shell: bash + run : | + # Populate defaults + echo "classic=false" >> "$GITHUB_OUTPUT" + echo "slots=''" >> "$GITHUB_OUTPUT" + echo "plugs=''" >> "$GITHUB_OUTPUT" + + # Check for classic confinement and update the output if the snap is classic + if [[ "$(cat snap/snapcraft.yaml | yq -r '.confinement')" == "classic" ]]; then + echo "classic=true" >> "$GITHUB_OUTPUT" + fi + + # Declare the common locations for plugs/slots declarations + plugs_files=("plug-declaration.json" ".github/plug-declaration.json") + slots_files=("slot-declaration.json" ".github/slot-declaration.json") + + for file in "${plugs_files[@]}"; do + if [[ -f "$file" ]]; then + echo "plugs=$file" >> "$GITHUB_OUTPUT" + fi + done + + for file in "${slots_files[@]}"; do + if [[ -f "$file" ]]; then + echo "slots=$file" >> "$GITHUB_OUTPUT" + fi + done + + - name: Review the built snap uses: diddlesnaps/snapcraft-review-action@v1 with: snap: ${{ steps.build.outputs.snap }} - isClassic: 'false' + isClassic: ${{ steps.parse.outputs.classic }} + plugs: ${{ steps.parse.outputs.plugs }} + slots: ${{ steps.parse.outputs.slots }} + + - name: Install the snap + if: ${{ inputs.install == 'true' }} + shell: bash + run: | + sudo snap install --classic --dangerous ${{ steps.build.outputs.snap }} From cf7018a47ae92d4240c856e9f141ccd3e3c40d11 Mon Sep 17 00:00:00 2001 From: Jon Seager Date: Sat, 2 Dec 2023 10:59:41 +0000 Subject: [PATCH 5/6] feat: make the release-to-candidate workflow aware of plugs/slots files --- release-to-candidate/action.yaml | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/release-to-candidate/action.yaml b/release-to-candidate/action.yaml index 91e4cf8..cd24d62 100644 --- a/release-to-candidate/action.yaml +++ b/release-to-candidate/action.yaml @@ -61,15 +61,40 @@ runs: version="$(cat snap/snapcraft.yaml | yq -r '.version')" echo "snap=${name}_${version}_${arch}.snap" >> "$GITHUB_OUTPUT" + + # Write the manifest file which is used by later steps + echo "name: ${name}" >> "manifest-${arch}.yaml" + echo "architecture: ${arch}" >> "manifest-${arch}.yaml" + + - name: Parse snap review information + id: parse + shell: bash + run : | + # Populate defaults echo "classic=false" >> "$GITHUB_OUTPUT" + echo "slots=''" >> "$GITHUB_OUTPUT" + echo "plugs=''" >> "$GITHUB_OUTPUT" + # Check for classic confinement and update the output if the snap is classic if [[ "$(cat snap/snapcraft.yaml | yq -r '.confinement')" == "classic" ]]; then echo "classic=true" >> "$GITHUB_OUTPUT" fi - # Write the manifest file which is used by later steps - echo "name: ${name}" >> "manifest-${arch}.yaml" - echo "architecture: ${arch}" >> "manifest-${arch}.yaml" + # Declare the common locations for plugs/slots declarations + plugs_files=("plug-declaration.json" ".github/plug-declaration.json") + slots_files=("slot-declaration.json" ".github/slot-declaration.json") + + for file in "${plugs_files[@]}"; do + if [[ -f "$file" ]]; then + echo "plugs=$file" >> "$GITHUB_OUTPUT" + fi + done + + for file in "${slots_files[@]}"; do + if [[ -f "$file" ]]; then + echo "slots=$file" >> "$GITHUB_OUTPUT" + fi + done - name: Review the built snap uses: diddlesnaps/snapcraft-review-action@v1 From 69daf83d8ab0fb7da5962b5240ef019a14711c54 Mon Sep 17 00:00:00 2001 From: Jon Seager Date: Sat, 2 Dec 2023 11:19:11 +0000 Subject: [PATCH 6/6] docs: add if statement to promote to stable example --- promote-to-stable/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/promote-to-stable/README.md b/promote-to-stable/README.md index 81b4e15..6ed3446 100644 --- a/promote-to-stable/README.md +++ b/promote-to-stable/README.md @@ -12,6 +12,10 @@ jobs: promote: name: โฌ†๏ธ Promote to stable runs-on: ubuntu-latest + if: | + ( !github.event.issue.pull_request ) + && contains(github.event.comment.body, '/promote ') + && contains(github.event.*.labels.*.name, 'testing') steps: - name: โฌ†๏ธ Promote to stable uses: snapcrafters/ci/promote-to-stable@main