Skip to content

valefar-on-discord is building binaries, building the docker image and drafting a release #13

valefar-on-discord is building binaries, building the docker image and drafting a release

valefar-on-discord is building binaries, building the docker image and drafting a release #13

Workflow file for this run

name: ci-build
run-name: ${{ github.actor }} is building binaries, building the docker image and drafting a release
on:
workflow_dispatch:
push:
tags:
- v*
# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds.
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-binaries:
runs-on: ${{ matrix.os }}
permissions:
id-token: write
contents: read
attestations: write
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04]
python-version: ["3.12"]
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
- name: Setup variables (Linux & macOS)
if: ${{ startsWith(matrix.os, 'ubuntu-') || startsWith(matrix.os, 'macos-') }}
env:
MATRIX_OS: '${{ matrix.os }}'
run: |
echo "PYTHONHASHSEED=42" >> "$GITHUB_ENV"
SHORT_SHA=$(echo ${{ github.sha }} | cut -c -7)
echo "SHORT_SHA=${SHORT_SHA}" >> "$GITHUB_ENV"
if [[ $MATRIX_OS == ubuntu-* ]] ;
then
BUILD_SYSTEM=linux
BUILD_CONFIGS_PATH=./build_configs/linux
fi
if [[ $MATRIX_OS == macos-* ]] ;
then
BUILD_SYSTEM=darwin
BUILD_CONFIGS_PATH=./build_configs/macos
brew install coreutils
fi
BUILD_ARCHITECTURE=amd64
if [[ $MATRIX_OS == *arm* ]] || [[ $MATRIX_OS == macos-latest ]] ;
then
BUILD_ARCHITECTURE=arm64
fi
BUILD_FILE_NAME=ethstaker_deposit-cli-${SHORT_SHA}-${BUILD_SYSTEM}-${BUILD_ARCHITECTURE}
mkdir "${BUILD_FILE_NAME}"
echo "BUILD_FILE_NAME=${BUILD_FILE_NAME}" >> "$GITHUB_ENV"
echo "BUILD_CONFIGS_PATH=${BUILD_CONFIGS_PATH}" >> "$GITHUB_ENV"
- name: Setup variables (Windows)
if: ${{ startsWith(matrix.os, 'windows-') }}
env:
MATRIX_OS: '${{ matrix.os }}'
run: |
echo "PYTHONHASHSEED=42" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
$env:SHORT_SHA = "${{ github.sha }}".Substring(0, 7)
echo ("SHORT_SHA=" + $env:SHORT_SHA) | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
if ($env:MATRIX_OS.Contains("arm")) {
$env:BUILD_ARCHITECTURE = "arm64"
}
else {
$env:BUILD_ARCHITECTURE = "amd64"
}
$env:BUILD_FILE_NAME = ("ethstaker_deposit-cli-" + $env:SHORT_SHA + "-windows-" + $env:BUILD_ARCHITECTURE)
echo ("BUILD_FILE_NAME=" + $env:BUILD_FILE_NAME) | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
mkdir $env:BUILD_FILE_NAME
$env:BUILD_FILE_NAME_PATH = (".\" + $env:BUILD_FILE_NAME)
echo ("BUILD_FILE_NAME_PATH=" + $env:BUILD_FILE_NAME_PATH) | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- name: Build on Linux & macOS
if: ${{ startsWith(matrix.os, 'ubuntu-') || startsWith(matrix.os, 'macos-') }}
run: |
python -m pip install --upgrade pip
pip install -r "${BUILD_CONFIGS_PATH}"/requirements.txt
pyinstaller --distpath ./"${BUILD_FILE_NAME}" "${BUILD_CONFIGS_PATH}"/build.spec
export ARCHIVE_FILE_NAME="${BUILD_FILE_NAME}".tar.gz
echo "ARCHIVE_FILE_NAME=${ARCHIVE_FILE_NAME}" >> "$GITHUB_ENV"
tar -zcvf "${ARCHIVE_FILE_NAME}" ./"${BUILD_FILE_NAME}"
mkdir -p output/artifacts
cp "${ARCHIVE_FILE_NAME}" output/artifacts
sha256sum "${ARCHIVE_FILE_NAME}" | head -c 64 > output/artifacts/"${ARCHIVE_FILE_NAME}".sha256
- name: Build on Windows
if: ${{ startsWith(matrix.os, 'windows-') }}
run: |
python -m pip install --upgrade pip
pip install -r build_configs/windows/requirements.txt
pyinstaller --distpath $env:BUILD_FILE_NAME .\build_configs\windows\build.spec
$env:ZIP_FILE_NAME = ($env:BUILD_FILE_NAME + ".zip")
echo ("ZIP_FILE_NAME=" + $env:ZIP_FILE_NAME) | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
Compress-Archive -Path $env:BUILD_FILE_NAME_PATH -DestinationPath $env:ZIP_FILE_NAME
mkdir output\artifacts
copy $env:ZIP_FILE_NAME output\artifacts
$env:CHECKSUM_FILE_NAME_PATH = ("output\artifacts\" + $env:ZIP_FILE_NAME + ".sha256")
certUtil -hashfile $env:ZIP_FILE_NAME SHA256 | findstr /i /v "SHA256" | findstr /i /v "CertUtil" > $env:CHECKSUM_FILE_NAME_PATH
- name: Generate artifacts attestation
uses: actions/attest-build-provenance@v1
with:
subject-path: output/artifacts/*
- name: Archive production artifacts
uses: actions/upload-artifact@v4
with:
name: binary-${{ matrix.os }}-${{ github.sha }}-${{ github.run_id }}
path: output/artifacts
build-and-push-docker:
runs-on: ubuntu-latest
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
permissions:
contents: read
packages: write
attestations: write
id-token: write
outputs:
metadata: ${{ steps.push.outputs.metadata }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here.
- name: Log in to the Container registry
# This pinned action came from docker/login-action@v3, releases can be found on https://github.com/docker/login-action/releases
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels.
- name: Extract metadata (tags, labels) for Docker
id: meta
# This pinned action came from docker/metadata-action@v5, releases can be found on https://github.com/docker/metadata-action/releases
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages.
# It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository.
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
- name: Build and push Docker image
id: push
# This pinned action came from docker/build-push-action@v6, releases can be found on https://github.com/docker/build-push-action/releases
uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# This step generates an artifact attestation for the image, which is an unforgeable statement about where and how it was built. It increases supply chain security for people who consume the image. For more information, see "[AUTOTITLE](/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds)."
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v1
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
create-release:
needs: [build-binaries, build-and-push-docker]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Download build binaries
uses: actions/download-artifact@v4
with:
path: assets/
pattern: binary-*
- name: Create draft release
uses: actions/github-script@v7
env:
DOCKER_IMAGE_METADATA: '${{ needs.build-and-push-docker.outputs.metadata }}'
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
var path = require('path');
var fs = require('fs');
const { DOCKER_IMAGE_METADATA, REGISTRY, IMAGE_NAME } = process.env;
const dockerMetadata = JSON.parse(DOCKER_IMAGE_METADATA);
var tagName = '';
var dockerVersion = '';
if (context.eventName == 'push') {
const tagRegex = /(?:refs\/tags\/)?(v\d+\.\d+\.\d+)$/;
const match = context.ref.match(tagRegex);
if (match) {
tagName = match[1];
dockerVersion = tagName;
} else {
core.setFailed(`Cannot extract the tag version from ref value '${context.ref}'.`);
}
} else if (context.eventName == 'workflow_dispatch') {
tagName = `dev-${context.actor}-${context.sha.substring(0, 7)}-${context.runId}`;
dockerVersion = dockerMetadata['containerimage.digest'].replace(':', '-');
} else {
core.setFailed(`Unhandled triggering event.`);
}
console.log(`Creating draft release for tag ${tagName}...`)
console.log(`tagName: ${tagName}`);
console.log(`context.sha: ${context.sha}`);
const { data: release } = await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: tagName,
target_commitish: context.sha,
draft: true,
generate_release_notes: true,
});
console.log(`Release ${release.id} created.`);
let binariesMap = new Map();
const emptyBinaryObject = {
system: null,
architecture: null,
binary_archive: null,
binary_archive_download_url: null,
binary_checksum: null,
binary_checksum_download_url: null,
attestation: null,
};
const windowsSystem = 'Windows';
const macOSSystem = 'macOS';
const linuxSystem = 'Linux';
const amd64Architecture = 'x86_64';
const arm64Architecture = 'aarch64';
binariesMap.set('windows-amd64', Object.assign({}, emptyBinaryObject, {
system: windowsSystem,
architecture: amd64Architecture,
}));
binariesMap.set('windows-arm64', Object.assign({}, emptyBinaryObject, {
system: windowsSystem,
architecture: arm64Architecture,
}));
binariesMap.set('darwin-amd64', Object.assign({}, emptyBinaryObject, {
system: macOSSystem,
architecture: amd64Architecture,
}));
binariesMap.set('darwin-arm64', Object.assign({}, emptyBinaryObject, {
system: macOSSystem,
architecture: arm64Architecture,
}));
binariesMap.set('linux-amd64', Object.assign({}, emptyBinaryObject, {
system: linuxSystem,
architecture: amd64Architecture,
}));
binariesMap.set('linux-arm64', Object.assign({}, emptyBinaryObject, {
system: linuxSystem,
architecture: arm64Architecture,
}));
console.log('Uploading release assets...');
const binaryPlatformRegex = /(\w+)-(\w+)(?=\.(?:zip|tar\.gz)(.sha256)?$)/;
const archivesGlobber = await glob.create('assets/*/*')
for await (const file of archivesGlobber.globGenerator()) {
console.log(`Uploading ${path.basename(file)} to the release ${release.id}`);
const fileName = path.basename(file);
const fileContent = fs.readFileSync(file);
const { data: asset } = await github.rest.repos.uploadReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: release.id,
name: fileName,
data: fileContent,
});
const match = fileName.match(binaryPlatformRegex);
if (match) {
const platform = `${match[1]}-${match[2]}`;
const binaryDetails = binariesMap.get(platform);
if (fileName.endsWith('.sha256')) {
binariesMap.set(platform, Object.assign({}, binaryDetails, {
binary_checksum: fileName,
binary_checksum_download_url: asset.browser_download_url,
}));
} else {
binariesMap.set(platform, Object.assign({}, binaryDetails, {
binary_archive: fileName,
binary_archive_download_url: asset.browser_download_url,
}));
}
}
}
const binariesTable = [
'| System | Architecture | Binary | Checksum |',
'|---------|--------------|--------------------|------------------------|'
];
binariesMap.forEach((details, platform) => {
if (
details.binary_archive !== null &&
details.binary_archive_download_url !== null &&
details.binary_checksum !== null &&
details.binary_checksum_download_url !== null
) {
const system = details.system;
const architecture = details.architecture;
const binaryName = details.binary_archive;
const binaryUrl = details.binary_archive_download_url;
const checksumName = details.binary_checksum;
const checksumUrl = details.binary_checksum_download_url;
const binaryAssetUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/releases/download/${tagName}/${binaryName}`;
const checksumAssetUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/releases/download/${tagName}/${checksumName}`;
console.log('binaryUrl');
console.log(binaryUrl);
console.log('binaryAssetUrl');
console.log(binaryAssetUrl);
console.log('checksumUrl');
console.log(checksumUrl);
console.log('checksumAssetUrl');
console.log(checksumAssetUrl);
binariesTable.push(`| ${system} | ${architecture} | [${binaryName}](${binaryAssetUrl}) | [sha256](${checksumAssetUrl}) |`);
}
});
const binariesTableContent = binariesTable.join('\n');
const dockerTable = [
'| Version | Name | Package |',
'|---------|------|---------|'
];
dockerTable.push(`| ${dockerVersion} | \`${REGISTRY}/${IMAGE_NAME}:${dockerVersion}\` | [Github Package](https://github.com/${context.repo.owner}/${context.repo.repo}/pkgs/container/ethstaker-deposit-cli) |`);
const dockerTableContent = dockerTable.join('\n');
const { data: workflowRun } = await github.rest.actions.getWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.runId,
});
let releaseBodyTemplate = fs.readFileSync('.github/release_template.md', { encoding: 'utf8'});
console.log('Removing comments in release template...');
releaseBodyTemplate = releaseBodyTemplate.replaceAll(/^\[comment\]:\s*<>\s*\((.*?)\)\s*$/gm, '');
releaseBodyTemplate = releaseBodyTemplate.trim();
let releaseBody = releaseBodyTemplate.replaceAll('`[GENERATED-RELEASE-NOTES]`', release.body);
releaseBody = releaseBody.replaceAll('`[BINARIES-TABLE]`', binariesTableContent);
releaseBody = releaseBody.replaceAll('`[WORKFLOW-RUN-URL]`', workflowRun.html_url);
releaseBody = releaseBody.replaceAll('`[DOCKER-TABLE]`', dockerTableContent);
console.log('Updating release body with generated content and template...');
const { data: updatedRelease } = await github.rest.repos.updateRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: release.id,
tag_name: tagName,
target_commitish: context.sha,
body: releaseBody,
});
console.log(`Release ${updatedRelease.id} updated. Explore it on ${updatedRelease.html_url}`);