diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index cd547f3..aad6264 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -4,6 +4,7 @@ on: push: branches: - "**" + - "!bump-version/**" - "!dependabot/**" tags: - 'v[0-9]+\.[0-9]+\.[0-9]+-?**' @@ -100,16 +101,28 @@ jobs: if: startsWith(github.ref, 'refs/tags/') || (github.ref == 'refs/heads/main' && contains(steps.interface_version.outputs.VERSION, '-')) run: make publish env: + PUBLISH_LOG: "${{ runner.temp }}/published.txt" REPOSITORY: "ghcr.io/${{ github.repository }}" VERSION: "${{ case(github.ref == 'refs/heads/main', steps.interface_version.outputs.VERSION, steps.tag_version.outputs.VERSION) }}" + - name: Draft release notes + run: | + { + echo "## Published components" + echo + echo "| File | Image |" + echo "| --- | --- |" + while read -r file image ; do + echo "| \`${file}\` | \`${image}\` |" + done < "${RUNNER_TEMP}/published.txt" + } > "${RUNNER_TEMP}/release-notes.md" + cat "${RUNNER_TEMP}/release-notes.md" - name: Draft GitHub Release if: startsWith(github.ref, 'refs/tags/') uses: softprops/action-gh-release@v3 with: draft: true + body_path: ${{ runner.temp }}/release-notes.md files: | - target/components/*.wasm - target/components/*/*.wasm components.tar fail_on_unmatched_files: true token: ${{ secrets.GITHUB_TOKEN }} diff --git a/Cargo.toml b/Cargo.toml index fa9bcf6..28a4dee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,9 @@ members = [ "crates/test-harness", ] +[workspace.package] +version = "0.1.1-dev" + [workspace.dependencies] bytes = "1" http = "1" diff --git a/Makefile b/Makefile index 8e35ec1..c12df8d 100644 --- a/Makefile +++ b/Makefile @@ -159,6 +159,8 @@ $(foreach dir,$(WKG_DIRS),$(eval $(call FETCH_WIT,$(dir)))) # sign published components with cosign, `SIGN=false` to push without signing, e.g. to a local registry SIGN ?= true +# append each published file and its image to this file, e.g. `gate.wasm ghcr.io/componentized/http/gate:0.1.0@sha256:...` +PUBLISH_LOG ?= # the files that can be published, e.g. gate.wasm, published from target/components/gate/gate.wasm PUBLISH_FILES := interface.wasm $(foreach component,$(filter-out dep-% test-%,$(COMPONENTS)),$(component).wasm $(component).debug.wasm) @@ -168,43 +170,5 @@ publish: $(addprefix publish-,$(PUBLISH_FILES)) .PHONY: $(addprefix publish-,$(PUBLISH_FILES)) $(addprefix publish-,$(PUBLISH_FILES)): publish-%: | $(call tool,wkg) -ifndef VERSION - $(error VERSION is undefined) -endif -ifndef REPOSITORY - $(error REPOSITORY is undefined) -endif - @$(eval FILE := $(@:publish-%=%)) - @$(eval COMPONENT := $(patsubst %.wasm,%,$(patsubst %.debug.wasm,%,$(FILE)))) -# components are in a directory of their own, the interface is not, e.g. gate/gate.wasm and interface.wasm - @$(eval COMPONENT_FILE := $(if $(filter interface.wasm,$(FILE)),$(FILE),$(COMPONENT)/$(FILE))) - @$(eval README := ${COMPONENTS_DIR}/$(dir $(COMPONENT_FILE))README.md) - @$(eval TITLE := $(subst /,:,$(GITHUB_REPOSITORY))$(if $(filter interface,$(COMPONENT)),,-$(COMPONENT))$(if $(filter %.debug.wasm,$(FILE)), (debug))) - @$(eval DESCRIPTION := $(shell head -n 3 "$(README)" | tail -n 1)) - @$(eval COMMIT := $(shell git rev-parse HEAD)) - @$(eval README_DIR := $(if $(wildcard components/$(COMPONENT)/README.md),/components/$(COMPONENT))) - @$(eval URL := https://github.com/${GITHUB_REPOSITORY}/tree/${COMMIT}${README_DIR}) - @$(eval REVISION := ${COMMIT}$(shell git diff --quiet HEAD || echo "+dirty")) - @$(eval COMPONENT_VERSION := $(if $(filter %.debug.wasm,$(FILE)),${VERSION}+debug,${VERSION})) - @$(eval TAG := $(patsubst v%,%,$(subst +,_,$(COMPONENT_VERSION)))) - @$(eval IMAGE := $(if $(filter interface.wasm,$(FILE)),${REPOSITORY}:${TAG},${REPOSITORY}/${COMPONENT}:${TAG})) - - @echo "::group::${FILE} -> ${IMAGE}" - @set -o pipefail ; \ - DIGEST=$$( \ - wkg oci push \ - --annotation "org.opencontainers.image.title=${TITLE}" \ - --annotation "org.opencontainers.image.description=${DESCRIPTION}" \ - --annotation "org.opencontainers.image.version=${COMPONENT_VERSION}" \ - --annotation "org.opencontainers.image.url=${URL}" \ - --annotation "org.opencontainers.image.source=https://github.com/${GITHUB_REPOSITORY}.git" \ - --annotation "org.opencontainers.image.revision=${REVISION}" \ - --annotation "org.opencontainers.image.licenses=Apache-2.0" \ - "${IMAGE}" \ - "${COMPONENTS_DIR}/${COMPONENT_FILE}" \ - 2>&1 \ - | tee /dev/stderr \ - | grep -o 'sha256:[a-f0-9]\{64\}' \ - ) && \ - $(if $(filter true,$(SIGN)),cosign sign --yes "${IMAGE}@$${DIGEST}",echo "Not signing ${IMAGE}@$${DIGEST}, SIGN=${SIGN}") - @echo "::endgroup::" + @VERSION="$(VERSION)" REPOSITORY="$(REPOSITORY)" COMPONENTS_DIR="$(COMPONENTS_DIR)" SIGN="$(SIGN)" PUBLISH_LOG="$(PUBLISH_LOG)" \ + scripts/publish.sh $* diff --git a/scripts/publish.sh b/scripts/publish.sh new file mode 100755 index 0000000..be4c9bc --- /dev/null +++ b/scripts/publish.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash + +# Publish a built component to an OCI registry, and sign it with cosign. +# +# VERSION= REPOSITORY= scripts/publish.sh +# +# The file is one of the files built into the components directory, components are in a directory +# of their own, the interface is not, e.g. gate.wasm from target/components/gate/gate.wasm and +# interface.wasm from target/components/interface.wasm. The interface is published to the +# repository, each component to a repository of its own under it, a debug build is tagged with a +# `_debug` suffix. +# +# interface.wasm -> ${REPOSITORY}:0.1.0 +# gate.wasm -> ${REPOSITORY}/gate:0.1.0 +# gate.debug.wasm -> ${REPOSITORY}/gate:0.1.0_debug +# +# VERSION the version to publish, a leading `v` is dropped from the tag, e.g. v0.1.0 +# REPOSITORY the repository to publish to, e.g. ghcr.io/componentized/http +# GITHUB_REPOSITORY the GitHub repository the components are built from, e.g. componentized/http +# COMPONENTS_DIR the directory the components are built into, defaults to target/components +# SIGN sign the published components with cosign, `false` to push without signing, +# e.g. to a local registry +# PUBLISH_LOG append the published file and its image to this file, e.g. +# `gate.wasm ghcr.io/componentized/http/gate:0.1.0@sha256:...` + +set -euo pipefail + +cd "$(dirname "$0")/.." + +file="${1:-}" +if [[ -z "$file" ]]; then + echo "usage: $0 , e.g. interface.wasm or gate.wasm" >&2 + exit 1 +fi +: "${VERSION:?VERSION is undefined}" +: "${REPOSITORY:?REPOSITORY is undefined}" +GITHUB_REPOSITORY="${GITHUB_REPOSITORY:-componentized/$(basename "$(git rev-parse --show-toplevel)")}" +COMPONENTS_DIR="${COMPONENTS_DIR:-target/components}" +SIGN="${SIGN:-true}" +PUBLISH_LOG="${PUBLISH_LOG:-}" + +component="${file%.wasm}" +component="${component%.debug}" +debug="" +[[ "$file" == *.debug.wasm ]] && debug=true + +if [[ "$file" == interface.wasm ]]; then + component_file="$file" + title="${GITHUB_REPOSITORY/\//:}" +else + component_file="${component}/${file}" + title="${GITHUB_REPOSITORY/\//:}-${component}" +fi +[[ -n "$debug" ]] && title="${title} (debug)" + +# the description is the line following the title in the readme +readme="${COMPONENTS_DIR}/$(dirname "$component_file")/README.md" +description=$(sed -n 3p "$readme") + +commit=$(git rev-parse HEAD) +revision="$commit" +git diff --quiet HEAD || revision="${commit}+dirty" +url="https://github.com/${GITHUB_REPOSITORY}/tree/${commit}" +[[ -f "components/${component}/README.md" ]] && url="${url}/components/${component}" + +component_version="$VERSION" +[[ -n "$debug" ]] && component_version="${VERSION}+debug" +tag="${component_version#v}" +tag="${tag//+/_}" + +if [[ "$file" == interface.wasm ]]; then + image="${REPOSITORY}:${tag}" +else + image="${REPOSITORY}/${component}:${tag}" +fi + +echo "::group::${file} -> ${image}" + +digest=$( + wkg oci push \ + --annotation "org.opencontainers.image.title=${title}" \ + --annotation "org.opencontainers.image.description=${description}" \ + --annotation "org.opencontainers.image.version=${component_version}" \ + --annotation "org.opencontainers.image.url=${url}" \ + --annotation "org.opencontainers.image.source=https://github.com/${GITHUB_REPOSITORY}.git" \ + --annotation "org.opencontainers.image.revision=${revision}" \ + --annotation "org.opencontainers.image.licenses=Apache-2.0" \ + "$image" \ + "${COMPONENTS_DIR}/${component_file}" \ + 2>&1 \ + | tee /dev/stderr \ + | grep -o 'sha256:[a-f0-9]\{64\}' +) + +if [[ -n "$PUBLISH_LOG" ]]; then + echo "${file} ${image}@${digest}" >> "$PUBLISH_LOG" +fi + +if [[ "$SIGN" == true ]]; then + cosign sign --yes "${image}@${digest}" +else + echo "Not signing ${image}@${digest}, SIGN=${SIGN}" +fi + +echo "::endgroup::"