Skip to content

Commit

Permalink
Add GH actions to assemble a multiarch manifest for amd64 rocks (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
addyess authored Jan 29, 2024
1 parent 3da6ed0 commit 86fc53d
Show file tree
Hide file tree
Showing 6 changed files with 485 additions and 0 deletions.
36 changes: 36 additions & 0 deletions .github/files/assemble-image-tags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
async function get_by_assoc(assoc, package_name, type, method) {
let containers
try {
core.info(`Looking up existing containers by ${type} ${assoc}/${package_name}`)
containers = (await method({[type]: assoc, package_type: "container", package_name})).data;
} catch (e) {
containers = [];
console.error(e);
}
return containers
}

async function get_containers(assoc, package_name) {
let by_org = await get_by_assoc(assoc, package_name, "org", github.rest.packages.getAllPackageVersionsForPackageOwnedByOrg)
let by_user = await get_by_assoc(assoc, package_name, "username", github.rest.packages.getAllPackageVersionsForPackageOwnedByUser)
return by_org.concat(by_user)
}

async function main(rockMetas){
const owner = context.repo.owner
const metas = await Promise.all(
rockMetas.map(
async meta => {
const versions = await get_containers(owner, meta.name)
const rockVersion_ = meta.version + "-ck"
const patchRev = versions.reduce((partial, v) =>
partial + v.metadata.container.tags.filter(t => t.startsWith(rockVersion_)).length, 0
)
meta.version = rockVersion_ + patchRev
core.info(`Number of containers tagged ${owner}/${meta.name}/${meta.version}: ${patchRev}`)
core.info(`Tagging image ${meta.image} with ${meta.version}`)
return meta
}
))
core.setOutput('rock-metas', JSON.stringify(metas))
}
73 changes: 73 additions & 0 deletions .github/files/create-and-push-manifest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
class RockImage {
constructor (image, arch) {
this.image = image
this.arch = arch
}

async import_image() {
console.info(` ⏬ pull image: ${this.image}`)
await exec.exec("docker", ["pull", this.image])
}

async annotate(target) {
console.info(` 🖌️ annotate manifest: ${target} ${this.arch}`)
await exec.exec("docker", ["manifest", "annotate", target, this.image, `--arch=${this.arch}`])
}
}

class RockComponent {
constructor (name, version, dryRun) {
this.name = name
this.version = version
this.dryRun = dryRun
this.imageVer = `${this.name}:${this.version}`
this.images = []
}

async create_manifest(target) {
const archs = this.images.map(i => i.arch)
const images = this.images.map(i => i.image)
console.info(` 📄 create manifest: ${target} ${archs.join(",")}`)
await exec.exec("docker", ["manifest", "create", target, images.join(' ')])
}

async push_manifest(target) {
console.info(` ⏫ push manifest: ${target}`)
console.info(`docker manifest push ${target}`)
if (this.dryRun != true ){
await exec.exec("docker", ["manifest", "push", target])
} else {
console.info(`Not pushing manifest ${target} -- because dryRun: ${this.dryRun}`)
}
}

async craft_manifest(target) {
for (const image of this.images) {
await image.import_image()
}
const targetImage = `${target.trim('/')}/${this.imageVer}`
await exec.exec("docker", ["manifest", "rm", targetImage], {ignoreReturnCode: true})
await this.create_manifest(targetImage)

for (const image of this.images) {
await image.annotate(targetImage)
}
await this.push_manifest(targetImage)
}
}

async function main(rockMetas, registry, dryRun) {
const owner = context.repo.owner
const metas = rockMetas
const containers = {}
for (const meta of metas) {
if (!containers.hasOwnProperty(meta.name)) {
containers[meta.name] = new RockComponent(meta.name, meta.version, dryRun)
}
containers[meta.name].images.push(new RockImage(meta.image, meta.arch))
}
for (const component of Object.values(containers)) {
console.info(`🖥️ Assemble Multiarch Image: ${component.name}`)
await component.craft_manifest(`${registry}/${owner}`)
}
}
58 changes: 58 additions & 0 deletions .github/workflows/assemble_multiarch_image.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Assemble Multiarch Manifest

on:
workflow_call:
inputs:
rock-metas:
description: List of maps featuring the built {name, version, path, arch, image}
type: string
default: "[]"
registry:
description: Container Registrying top-level domain
type: string
default: ghcr.io
dry-run:
description: Don't actually push the manifest, just print what would be pushed
type: string
default: true

jobs:
create-multiarch-manifest:
name: Create Mulitarch Manifest
runs-on: ubuntu-22.04
steps:
- uses: actions/[email protected]
- id: assemble-image-tags-js
uses: juliangruber/read-file-action@v1
with:
path: .github/files/assemble-image-tags.js
trim: true
- name: Assemble Image Tags
id: assemble-image-tags
uses: actions/[email protected]
with:
script: |
const rockMetas = JSON.parse(`${{ inputs.rock-metas }}`)
${{ steps.assemble-image-tags-js.outputs.content }}
await main(rockMetas)
- name: Login to Container Registry
uses: docker/[email protected]
with:
registry: ${{ inputs.registry }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- id: create-and-push-manifest-js
uses: juliangruber/read-file-action@v1
with:
path: .github/files/create-and-push-manifest.js
trim: true
- name: Create and Push Manifests
id: create-and-push-manifest
uses: actions/[email protected]
with:
script: |
const registry = '${{ inputs.registry }}'
const dryRun = ${{ inputs.dry-run }}
const rockMetas = JSON.parse(`${{ steps.assemble-image-tags.outputs.rock-metas }}`)
${{ steps.create-and-push-manifest-js.outputs.content }}
await main(rockMetas, registry, dryRun)
Loading

0 comments on commit 86fc53d

Please sign in to comment.