Skip to content

CI - Build MinIO test image from official source - #10765

Merged
potatoqualitee merged 7 commits into
developmentfrom
ci-minio-image-pgsty
Sep 30, 2026
Merged

potatoqualitee merged 7 commits into
developmentfrom
ci-minio-image-pgsty

Conversation

@andreasjordan

@andreasjordan andreasjordan commented Sep 27, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Build the MinIO CI server image locally from the final official MinIO source release instead of pulling another prebuilt server image from a registry.

Both integration workflows now:

  • install the pinned Go 1.24.8 toolchain;
  • clone the exact official MinIO tag and verify its full commit;
  • run go mod download, go mod verify, and the upstream make build target;
  • assert the source tree is unchanged after the build and that the resulting binary reports the pinned release and commit;
  • create a runner-local scratch image containing the binary plus LICENSE, NOTICE, and CREDITS;
  • use that local image without publishing or caching it; and
  • verify the already-pinned official mc binary's SHA-256 before executing it.

The Docker network, minio hostname, container ports, credentials, TLS certificate mount, and server /data command are unchanged. The MinIO ports are now published on the runner's loopback interface only, and the S3 job now fails when its Pester run fails instead of discarding the result.

Why MinIO is here

MinIO is a test fixture, not a dbatools runtime dependency. #10126 introduced it so SQL Server 2022 backup and restore commands could be tested against a real S3-compatible endpoint. #10137 added HTTPS and broader restore coverage. The external-table workflow also uses the same real S3 boundary.

Keeping the fixture containerized preserves an important part of that test contract: SQL Server and MinIO share localnet, SQL Server reaches https://minio:9000, and the mounted certificate is valid for DNS:minio. Installing MinIO directly on the runner would require changing routing, endpoint names, and certificate handling in addition to changing the supply mechanism.

Why this failed twice

These were distribution failures upstream of dbatools, not regressions in the PowerShell commands:

  1. The original workflows pulled minio/minio:RELEASE.2025-09-07T16-13-09Z from Docker Hub and downloaded mc from dl.min.io.
  2. On 2026-09-11, the Docker Hub repository returned 404 and the dl.min.io client URL returned 410. #10688 moved the server to quay.io/minio/minio and the client to its official pinned GitHub release asset.
  3. By 2026-09-27, Quay rejected anonymous repository and manifest requests with HTTP 401, and the workflows failed before tests started. That is a second server-image supply failure only 16 days after the first one.

Changing to another anonymously pulled image would preserve the same failure mode. It would also add a new publisher decision: the proposed pgsty/minio:RELEASE.2026-08-04T00-00-00Z was the last release under that image name. PGSTY renamed the project and every delivery surface to pgsty/silo two days later; its 2026-08-06 release notes also say complete license-compliance materials were added to every artifact in that later release.

MinIO's own community distribution has meanwhile moved to source-only. The official repository says precompiled community releases are no longer provided and recommends building from source. The selected final official release is pinned to full commit 9e49d5e7a648f00e26f2246f4dc28e6b07f8c84a, requires Go 1.24.8, and carries the last security fix released to the community edition. The repository has since been archived, and seven advisories published in March and April 2026 list this release as affected and are fixed only in AIStor. That is acceptable for an ephemeral fixture that only the runner can reach; it is not a release to run anywhere else.

This removes the server-image registry from the critical path. It does not pretend CI is network-free: the workflow still needs GitHub and the pinned Go module sources to compile the fixture. The source clone comes from the archived minio/minio repository, so it would fail if that repository were withdrawn. dbatools does not mirror or republish the source.

Supply-chain controls

  • Exact MinIO tag, full source commit, Go version, setup action commit, and mc checksum are repository-pinned.
  • The checked-out tag and commit are both verified before compilation.
  • GOTOOLCHAIN=local prevents an implicit toolchain substitution.
  • go mod download fetches the modules and checks each one against go.sum; go mod verify then confirms the cached copies are unaltered.
  • The source tree is checked for changes after the build, and the binary's reported release and commit are checked before packaging.
  • The runtime image is built FROM scratch and carries OCI source, version, revision, and license labels.
  • The image contains the upstream LICENSE, NOTICE, and CREDITS files.
  • The image remains local to the runner; there is no registry push or external image cache, and docker run --pull=never makes a missing local image fail instead of falling back to a registry.
  • A repository contract check guards both workflows' trigger paths, provenance pins, checksum, runtime image, and preserved network/TLS arguments, and rejects publishing or exporting the image or binary from either workflow.
  • MinIO's ports are published on 127.0.0.1 only; SQL Server reaches it as minio:9000 over localnet. The contract check rejects any other published port or host networking for the MinIO container.
  • Changes to the shared build helper or Dockerfile now trigger both real integration workflows.

License review

MinIO and mc are AGPL-3.0. The engineering conclusion is that compiling and running the unmodified tagged MinIO source as a separate, ephemeral CI server does not make dbatools an AGPL work: the programs remain separate processes and communicate through the public S3 protocol. AGPL section 2 permits making and running a covered work that is not conveyed, without conditions. The image is not published or retained as a downloadable artifact, and the official mc asset is consumed rather than redistributed.

The conservative compliance measures here are to retain the upstream license and notice material, identify the exact source and its AGPL-3.0-or-later license in OCI metadata, make no MinIO source changes, and keep the resulting image runner-local. If dbatools later publishes the binary or image, that would be a different distribution posture and would require providing the complete corresponding source and satisfying the AGPL object-code distribution requirements. This is an engineering compliance assessment, not legal advice; see MinIO's compliance notice and AGPL sections 2, 5, 6, and 13.

Verification

Local checks completed:

  • MinIO supply-chain contract check
  • actionlint across all 19 workflows
  • full-SHA GitHub Action pin check
  • Bash syntax check
  • Git diff whitespace check

The first GitHub Actions run proved the post-build assertion was useful: upstream defaults a locally compiled binary's embedded prefix to DEVELOPMENT. The build now sets upstream's MINIO_RELEASE=RELEASE input, the assertion sees the exact pinned release, and the corrected runs pass.

GitHub Actions completed both real integration lanes successfully for both the push and pull-request events:

  • S3 backup/restore with SQL Server 2022, TLS, and mc bucket setup
  • SQL Server 2025 external-table coverage against the same S3-compatible fixture

References

quay.io/minio/minio no longer answers anonymous pulls ("unauthorized"), so
the S3 and external table workflows fail before any test runs, as they did
when minio/minio left Docker Hub. pgsty/minio is the maintained community
fork with the same entrypoint and user, pinned by release tag.

(do Backup-DbaDatabase)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@potatoqualitee

Copy link
Copy Markdown
Member

i like the more durable option as we wont have to rely on an unofficial repo

Compile the final official MinIO release into an ephemeral local image, include license notices, and verify the pinned mc checksum so both integration workflows avoid third-party server registries.

(do integration-tests-s3, integration-tests-external-table)
@potatoqualitee potatoqualitee changed the title CI - Pull MinIO from the community fork pgsty/minio CI - Build MinIO test image from official source Sep 30, 2026
@potatoqualitee

Copy link
Copy Markdown
Member

Update: I converted this PR from the proposed pgsty/minio image swap to a pinned build from official MinIO source, and the real integration workflows now pass.

Why MinIO exists here

MinIO is not a dbatools runtime dependency. It was introduced in #10126 to give the SQL Server 2022 backup/restore tests a real S3-compatible boundary; #10137 expanded that coverage to HTTPS and additional restore behavior. The external-table workflow now uses the same kind of boundary.

Keeping the fixture in a container matters to the current test contract. SQL Server and MinIO share the localnet Docker network, SQL Server connects to https://minio:9000, and the generated certificate is valid for DNS:minio. Installing MinIO directly on the GitHub runner would also require changing routing, endpoint naming, and certificate handling. Building the container locally preserves all of that behavior.

Why this broke twice

The recent failures were distribution failures, not dbatools command regressions:

  1. The original workflows pulled minio/minio:RELEASE.2025-09-07T16-13-09Z from Docker Hub and downloaded mc from dl.min.io.
  2. On 2026-09-11, the Docker Hub repository returned 404 and the dl.min.io client URL returned 410. #10688 moved the server to MinIO's Quay image and moved mc to its pinned official GitHub release asset.
  3. By 2026-09-27, anonymous Quay repository and manifest requests returned 401, and both workflows failed before running any tests.

That is two server-image supply failures in 16 days. Both happened because a registry-hosted image was part of the critical path. Replacing Quay with another registry image would move the dependency without removing the failure mode.

The originally proposed pgsty/minio:RELEASE.2026-08-04T00-00-00Z also has two concerns:

  • it was the final release under the pgsty/minio delivery name; PGSTY renamed the project and its images to pgsty/silo two days later; and
  • PGSTY's 2026-08-06 release notes say that release added complete license-compliance materials to every artifact, so the proposed image predates that stated improvement.

This does not mean the maintained Silo fork is unsuitable. It means adopting it should be a deliberate compatibility and maintainer-trust decision, not another emergency two-line image substitution.

What the PR does now

Both workflows build the final official MinIO release, RELEASE.2025-10-15T17-29-55Z, pinned to full commit 9e49d5e7a648f00e26f2246f4dc28e6b07f8c84a and Go 1.24.8.

The shared build path now:

  • shallow-clones the exact official release tag;
  • verifies both the checked-out tag and full commit;
  • requires the exact Go toolchain and sets GOTOOLCHAIN=local;
  • runs go mod verify;
  • invokes the upstream build with MINIO_RELEASE=RELEASE;
  • verifies that minio --version reports the exact pinned release and commit;
  • creates a minimal runner-local FROM scratch image;
  • includes upstream LICENSE, NOTICE, and CREDITS files;
  • adds OCI source, version, revision, and license labels; and
  • never pushes the image or exports it to an external cache.

The official pinned mc release asset remains in use, but its published SHA-256 is now checked before the binary is made executable.

The MinIO container name, hostname, Docker network, ports, credentials, TLS mount, and server /data invocation are unchanged. Changes to the shared build helper or Dockerfile also now trigger both real integration workflows.

License posture

MinIO and mc are AGPL-3.0. The engineering assessment is that compiling and running the unmodified tagged MinIO source as a separate, ephemeral CI server does not make dbatools an AGPL work: the programs remain separate processes and communicate through the public S3 protocol.

The built image remains local to the GitHub runner and is not published as a container or workflow artifact. The conservative compliance steps are to retain the upstream license/notice materials, identify the exact source and revision, make no MinIO source modifications, and avoid redistribution.

If dbatools later publishes the binary or image, that is a different distribution posture. We would then need to provide the complete corresponding source for the conveyed binary and meet the AGPL object-code distribution requirements. This is an engineering compliance assessment rather than legal advice; the detailed references are in the PR body.

What CI found

The first source-build run failed at the post-build version assertion. Compilation had succeeded, but upstream's build metadata generator defaults locally compiled binaries to a DEVELOPMENT prefix unless MINIO_RELEASE=RELEASE is supplied. The assertion caught that before the image was used.

The build now supplies that upstream release input, and the contract check requires it. The corrected image reports the exact pinned release.

Verification

The real boundary tests pass for both GitHub event paths:

The MinIO supply-chain contract, actionlint across all workflows, full-SHA action pinning, Bash syntax, and whitespace checks also pass. An independent review found no remaining Critical or Important issues.

The branch is ready for maintainer review. I have not merged it.

@potatoqualitee
potatoqualitee marked this pull request as ready for review September 30, 2026 12:32
go mod verify ran against an empty module cache and verified nothing, so
download the modules first. Assert the source tree is unchanged after the
build and that the binary reports the pinned commit, not only the release.

Run the image with --pull=never so a missing local build fails instead of
falling back to a registry. Label the image AGPL-3.0-or-later, which is what
the MinIO source headers grant.

The contract check now accepts any full-SHA setup-go pin so Dependabot bumps
pass, and rejects publishing or exporting the image or binary from either
workflow as well as from the build helper.

(do integration-tests-s3, integration-tests-external-table)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@potatoqualitee

potatoqualitee commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

Review: licensing statement and code

I checked the license posture above against the upstream repositories and reviewed the code. The licensing statement holds. The review turned up two small licensing corrections, one inaccurate sentence in the description, and several code issues. All of them are fixed in d6b6a27 and the description is updated to match. Both integration lanes pass on that commit.

This is an engineering check, not legal advice.

Licensing claims

Claim Result What was checked
MinIO and mc are AGPL-3.0 True LICENSE at both pinned tags is AGPLv3.
Building and running it does not make dbatools an AGPL work Sound The PR adds only a build recipe. Nothing in it is MinIO code, and the Dockerfile is not derived from upstream's.
Unmodified source True, and now enforced The helper clones, verifies the commit and builds. MINIO_RELEASE=RELEASE is upstream's own build input and only sets a version string, so section 13 is not triggered.
Image stays runner-local and is not published True, and now enforced Neither workflow uploads an artifact, pushes or saves an image, or uses a cache. Both run on ubuntu-latest with permissions: contents: read.
LICENSE, NOTICE, CREDITS retained True All three exist at the tag and are copied in. Upstream's own release image ships only LICENSE and CREDITS.
mc is consumed, not redistributed True It is downloaded straight from the minio/mc release. I hashed the asset; it matches the pinned checksum and the published .sha256sum.
Publishing later would trigger section 6 obligations True
COMPLIANCE.md link and the "not legal advice" framing Accurate The file exists on master. It says MinIO will not make the determination and to use counsel.

Corrections made:

  • SPDX label. The image was labelled AGPL-3.0-only. MinIO's source headers grant "version 3 of the License, or (at your option) any later version", which is AGPL-3.0-or-later. The Dockerfile and the contract check now use that.
  • Section 2. The description cited sections 5, 6 and 13. The section that carries the argument is section 2, which permits making and running a covered work that is not conveyed, without conditions. It is now cited.
  • "Includes the final upstream security fix." It is the last fix released to the community edition. Seven advisories published in March and April 2026 list this release as affected and are fixed only in AIStor: CVE-2026-33322, CVE-2026-33419, CVE-2026-34204, CVE-2026-39414, CVE-2026-40344, CVE-2026-41145 and CVE-2026-42600. That is fine for an ephemeral fixture only the runner can reach, and the description now says so.

Code changes in d6b6a27

  1. go mod verify was verifying nothing. It ran before any module was downloaded, and with an empty cache it has nothing to check. In the earlier run it printed "all modules verified" two seconds after the clone, and all 260 downloads came afterwards during make build. The helper now runs go mod download first. In the new run only the 19 modules for upstream's debugging tools download during the build.
  2. The contract check would have failed on the next Dependabot bump. It hard-coded the setup-go commit and version comment, and dependabot.yml groups all action bumps weekly. It now accepts any full-SHA pin; Test-GitHubActionsPins.ps1 already requires full SHAs.
  3. Non-publication is now enforced. The forbidden list covered only the build helper and three patterns. It now also rejects docker image push, docker save, --push and actions/upload-artifact, and it scans both workflows as well as the helper.
  4. The helper now checks the source tree is unchanged after the build, so a patch step added before make build fails the job.
  5. The version assertion now checks the commit too, which the comment above already described.
  6. docker run --pull=never in both workflows. The tag resolves to docker.io/dbatools/minio-ci, which does not exist today, so a skipped build already failed rather than pulled. The flag keeps it that way if that name is ever published.
  7. Style: removed the blank lines between parameters in Get-YamlBlock and replaced $_ with $PSItem.

Verification

  • Built the pinned release in a golang:1.24.8 container: go mod verify before the download reports success with an empty cache, the tree is clean after make build, and minio --version prints commit-id=9e49d5e7a648f00e26f2246f4dc28e6b07f8c84a.
  • Ran the contract check against 14 mutated copies. It passes a simulated setup-go bump. A patch step added to the helper also passes, because the check only requires lines to be present; the new source-tree check in the helper is what stops that one. It fails each of these: a mutable setup-go tag, a push step, an artifact upload, docker image push, docker save, a revert to the Quay image, a removed --pull=never, a removed or reordered go mod download, the old SPDX label, and a removed source-tree or commit assertion.
  • S3 backup/restore: push run and pull-request run.
  • External tables: push run and pull-request run.
  • The contract check, actionlint and the pin check pass in GitHub Actions Security.
  • The build step took 2m02s, against 2m22s before, so the extra download costs nothing measurable.
  • The ci-azure test matrix was still running when this was posted.

Not changed

  • The source still comes from one vendor. minio/minio and minio/mc are both archived, and the README says the repository is no longer maintained. This is the same vendor whose Docker Hub and Quay images went away. If either repository is withdrawn, these workflows fail again and will need a new source. Nothing is mirrored or published to guard against that: dbatools does not redistribute MinIO in any form, which keeps the license posture above exactly as stated.
  • Build cost. The build is about two minutes per run in both workflows. Roughly 30 seconds of that is upstream's build-debugging tools, which the fixture does not use. Keeping the upstream make build target seemed worth that.
  • Line endings in integration-tests-s3.yml. The file mixes CRLF and LF and this PR adds LF lines to it. I tried restoring CRLF on the touched lines, but git diff --check reports CRLF on changed lines as trailing whitespace, so I left it as it was.

The S3 workflow discarded the Pester result, so the job stayed green
when tests failed. It now checks the result the same way the
external-table workflow does.

Both workflows published the MinIO ports on every host interface. They
are now bound to 127.0.0.1; SQL Server still reaches minio:9000 over
localnet. The contract check requires the loopback binding and rejects
any other published port or host networking for the MinIO container.

(do integration-tests-s3, integration-tests-external-table)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@potatoqualitee

Copy link
Copy Markdown
Member

Follow-up in bf23744, from a second review pass of d6b6a27. Two issues, both older than this PR but both relevant to what it claims.

The S3 job could not fail on a failed test. The last step was $null = Invoke-Pester ... -PassThru, unchanged since #10126, so the job stayed green whatever Pester reported. It now keeps the result and throws unless it passed, the same way the external-table workflow already does. The S3 runs cited earlier as verification were real passes: both logs show Tests Passed: 18, Failed: 0.

MinIO was published on every host interface. Both workflows used -p 9000:9000 (S3 also -p 9001:9001), so the description's "only the runner can reach" was not enforced by anything. The ports are now bound to 127.0.0.1. SQL Server still reaches minio:9000 over localnet, and the host-side curl, mc and AWS SDK calls already use localhost:9000.

Test-MinioCiSupplyChain.ps1 now requires the loopback binding and rejects any other published port, -P, or host networking on the MinIO container. Before this it required the unrestricted -p 9000:9000 spelling.

Checked locally:

  • With one deliberately failing test, the old step exits 0 and the new step exits 1.
  • A container with the same network shape, published on 127.0.0.1 only, is reachable from the host via localhost and from a peer container by name, and not on the host's non-loopback address.
  • The contract check passes on the change and fails on seven mutations that re-expose a port.

Not changed: integration-tests.yml discards the Pester result the same way in its Windows and macOS steps (lines 259, 269, 331). That is unrelated to MinIO, so it is left for a separate change.

The loop piped docker logs into grep -q. grep -q exits on the first
match, and with pipefail the resulting broken pipe in docker logs made
the test false even though SQL Server was ready, so the step timed out
after two minutes at random. grep now reads the whole log.

(do integration-tests-external-table)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@potatoqualitee

Copy link
Copy Markdown
Member

9aa49ec fixes a flaky wait loop that failed the external-table push run on bf23744 (attempt 1; the rerun passed).

The step that starts SQL Server 2025 tested readiness with docker logs mssql1 2>&1 | grep -q "SQL Server is now ready...". Bash steps run with pipefail, and grep -q exits on the first match. When docker logs is still writing at that point it dies on the broken pipe, the pipeline counts as failed, and the loop decides SQL Server is not ready.

In the failed run SQL Server logged the ready line at 13:58:09, five seconds after the container started, and the loop still ran its full two minutes and exited 1. The loop now uses grep ... > /dev/null, which reads the whole log. This predates the PR and is unrelated to MinIO; it is fixed here because it fails this PR's own checks at random.

@potatoqualitee
potatoqualitee merged commit bf77920 into development Sep 30, 2026
30 checks passed
@potatoqualitee
potatoqualitee deleted the ci-minio-image-pgsty branch September 30, 2026 15:42
@potatoqualitee

Copy link
Copy Markdown
Member

approved by codex 👍🏼

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants