ci: build each arch natively and publish them together

Build and test each arch on its own runner (ubuntu-latest and
ubuntu-24.04-arm) instead of under QEMU, with osixia/dagger-build OCI
archives: on tags, build jobs export them as artifacts, and the publish
job deploys them as a single multi-arch image per tag.

Integration scenarios now run on every built image, so on arm64 too:
each runner builds its own arch, so the engine platform filter never
skipped anything.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Bertrand Gouny
2026-10-16 23:47:50 +02:00
co-authored by Claude Opus 5.5
parent 5073cc319f
commit 5df4f23a40
6 changed files with 56 additions and 34 deletions
+45 -7
View File
@@ -60,11 +60,18 @@ jobs:
go-version-input: '1.26.9'
work-dir: build
# each arch is built and tested natively on its own runner instead of under QEMU
build:
name: build
if: "!startsWith(github.ref, 'refs/tags/')"
runs-on: ubuntu-latest
name: build (${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
needs: [lint, test, shellcheck, govulncheck]
strategy:
matrix:
include:
- arch: amd64
runner: ubuntu-latest
- arch: arm64
runner: ubuntu-24.04-arm
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
@@ -72,18 +79,39 @@ jobs:
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: '1.26.9'
cache-dependency-path: build/go.sum
- name: Vendor build go dependencies
working-directory: build
run: go mod vendor
# on tags, images are only exported as OCI archives: the publish job deploys them together
- name: Run tests
working-directory: build
run: go run main.go provider github ../Dockerfile --github-ref "${GITHUB_REF}" ${{ vars.OSX_GITHUB_FLAGS }}
env:
ARCH: ${{ matrix.arch }}
GITHUB_TOKEN: ${{ github.ref_type == 'tag' && secrets.GITHUB_TOKEN || '' }}
OSX_GITHUB_FLAGS: ${{ vars.OSX_GITHUB_FLAGS }}
run: |
flags=(--arches "${ARCH}" --github-ref "${GITHUB_REF}")
if [ "${GITHUB_REF_TYPE}" = "tag" ]; then
flags+=(--github-token "${GITHUB_TOKEN}" --oci-export-to ../oci)
fi
# shellcheck disable=SC2086
go run main.go provider github ../Dockerfile "${flags[@]}" ${OSX_GITHUB_FLAGS}
- name: Upload OCI archives
if: github.ref_type == 'tag'
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: oci-${{ matrix.arch }}
path: oci/
if-no-files-found: error
retention-days: 1
compression-level: 0
publish:
name: publish
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
if: github.event_name == 'push' && github.ref_type == 'tag'
runs-on: ubuntu-latest
needs: [lint, test, shellcheck, govulncheck]
needs: [build]
permissions:
contents: write
id-token: write
@@ -101,16 +129,26 @@ jobs:
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
- name: Install syft
uses: anchore/sbom-action/download-syft@66cbf4bc1f1c0d2edc94016e65bc221b6bb0ad6c # v0.24.3
- name: Download OCI archives
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
pattern: oci-*
path: oci/
merge-multiple: true
- name: Vendor build go dependencies
working-directory: build
run: go mod vendor
# deploys the images tested by the build jobs as a single multi-arch image per tag
- name: Run pipeline
working-directory: build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
run: go run main.go provider github ../Dockerfile --github-ref "${GITHUB_REF}" --github-token "${GITHUB_TOKEN}" --changelog ../CHANGELOG.md --username "${REGISTRY_USERNAME}" --password "${REGISTRY_PASSWORD}" --digests-file "${DIGESTS_FILE}" ${{ vars.OSX_GITHUB_FLAGS }}
OSX_GITHUB_FLAGS: ${{ vars.OSX_GITHUB_FLAGS }}
run: |
# shellcheck disable=SC2086
go run main.go provider github ../Dockerfile --github-ref "${GITHUB_REF}" --github-token "${GITHUB_TOKEN}" --oci-import-from ../oci --changelog ../CHANGELOG.md --username "${REGISTRY_USERNAME}" --password "${REGISTRY_PASSWORD}" --digests-file "${DIGESTS_FILE}" ${OSX_GITHUB_FLAGS}
- name: Sign images and generate SBOM
env:
COSIGN_EXPERIMENTAL: '1'
+1
View File
@@ -12,6 +12,7 @@ Project-specific changes only. See upstream [OpenLDAP release notes](https://www
- Build the CI/CD tool with `osixia/dagger-build` (replaces `osixia/container-baseimage/build`) and Go 1.26.9.
- Add integration tests: default bootstrap, all compatible modules and schemas, every bootstrap feature on a 3-node replicated cluster with TLS required, backup/restore and upgrades (simulated and from previous releases).
- Harden the CI workflow: read-only default permissions, actions pinned by commit SHA, images only published from tags, Dependabot updates.
- Build and test each architecture natively on its own runner instead of under QEMU, including the integration tests on arm64, and publish them together as a single multi-arch image.
- Allow authenticated users to read `entry`, `objectClass`, `ou`, `o` and `dc` in addition to `cn`, `sn`, `givenName`, `mail` and `uid` in the default database ACL (new bootstraps only).
- Set `OPENLDAP_BOOTSTRAP_PPOLICY_DEFAULT_CHECK_QUALITY` to `1` (was `0`) so `pwdMinLength` is enforced on clear text password changes; pre-hashed passwords are still accepted (new bootstraps only).
- Fail bootstrap with an explicit error when `OPENLDAP_BOOTSTRAP_SUFFIX` does not start with `dc=`, instead of creating a root entry with an empty `dc`.
+5 -3
View File
@@ -13,10 +13,12 @@ go mod vendor
go run main.go job test ../Dockerfile
```
Limit a local run with `--arches amd64`.
Limit a local run with `--arches amd64`. Pass the arch of the host: scenarios run on every
built image, and other arches would be emulated. The CI builds and tests each arch on its
own native runner, then publishes them together from their OCI archives
(`--oci-export-to`, `--oci-import-from`).
After the shared version check, `tests/` runs integration scenarios on the image built for
the engine platform:
After the shared version check, `tests/` runs integration scenarios on each built image:
- default bootstrap;
- alternative schemas (`nis`, `virtualMail`);
+1 -1
View File
@@ -4,7 +4,7 @@ go 1.26.9
require (
dagger.io/dagger v0.21.10
github.com/osixia/dagger-build v0.0.0-20261015194426-55e8bdd25dbb
github.com/osixia/dagger-build v0.0.0-20261016204440-cdddcccbc11c
)
require (
+2 -2
View File
@@ -37,8 +37,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/osixia/dagger-build v0.0.0-20261015194426-55e8bdd25dbb h1:V8FLn00GbtrHvOTII63DaCSZDoi2zQvmIztr20FmOw4=
github.com/osixia/dagger-build v0.0.0-20261015194426-55e8bdd25dbb/go.mod h1:gxxc7tVjlUVYPeBpDBdWs1MFHuP48AR6zes7F1NIMKI=
github.com/osixia/dagger-build v0.0.0-20261016204440-cdddcccbc11c h1:xxoEJ8RLtMVqK5WSHRTSxXPL9z9bh9B3EAQW5/hVyQM=
github.com/osixia/dagger-build v0.0.0-20261016204440-cdddcccbc11c/go.mod h1:gxxc7tVjlUVYPeBpDBdWs1MFHuP48AR6zes7F1NIMKI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+2 -21
View File
@@ -26,28 +26,13 @@ var scenarios = []scenario{
{"upgrade from previous releases", UpgradeFromPreviousReleases},
}
// Run checks OpenLDAP behavior after the reusable job's version check, only on the engine
// platform: emulated images are slow and fail as Dagger services.
// Run checks OpenLDAP behavior after the reusable job's version check. CI builds each arch
// on a native runner: run locally with --arches matching the host to avoid emulation.
func Run(ctx context.Context, client *dagger.Client, builds []*job.BuildResult) error {
enginePlatform, err := client.DefaultPlatform(ctx)
if err != nil {
return fmt.Errorf("engine platform: %w", err)
}
tested := 0
for _, build := range builds {
for _, image := range build.Containers {
platform, err := image.Platform(ctx)
if err != nil {
return fmt.Errorf("image platform: %w", err)
}
if platform != enginePlatform {
continue
}
tested++
for _, s := range scenarios {
log.Printf("scenario: %s ...", s.name)
sctx, cancel := context.WithTimeout(ctx, 10*time.Minute)
@@ -61,9 +46,5 @@ func Run(ctx context.Context, client *dagger.Client, builds []*job.BuildResult)
}
}
if tested == 0 && len(builds) > 0 {
return fmt.Errorf("no image built for the engine platform %v", enginePlatform)
}
return nil
}