Compare commits

..

175 Commits

Author SHA1 Message Date
Miloslav Trmač
98e4aa395f Merge pull request #2794 from containers/renovate/common-image-and-storage-deps
fix(deps): update common, image, and storage deps to 0e2aefd
2026-02-03 17:10:16 +01:00
renovate[bot]
800ea987b3 fix(deps): update common, image, and storage deps to 0e2aefd
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-03 15:28:40 +00:00
Paul Holzinger
12bd9fbb47 Merge pull request #2795 from mtrmac/sq-error
Update tests for a changed error message
2026-02-03 16:24:53 +01:00
Miloslav Trmač
a64f780f83 Update tests for a changed error message
008d971bc0
changed the text we were looking for.

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2026-02-02 13:56:34 +01:00
Miloslav Trmač
5f9e5d79be Merge pull request #2792 from containers/renovate/common-image-and-storage-deps
fix(deps): update common, image, and storage deps to b5801a6
2026-01-26 14:47:13 +01:00
renovate[bot]
56c4a65ec0 fix(deps): update common, image, and storage deps to b5801a6
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-25 01:42:35 +00:00
Miloslav Trmač
7c6e1eb524 Merge pull request #2790 from containers/renovate/common-image-and-storage-deps
fix(deps): update common, image, and storage deps to b2572af
2026-01-19 17:04:02 +01:00
renovate[bot]
d395f3eb76 fix(deps): update common, image, and storage deps to b2572af
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-18 00:43:53 +00:00
Miloslav Trmač
fabe041fad Merge pull request #2787 from containers/renovate/github.com-sirupsen-logrus-1.x
fix(deps): update module github.com/sirupsen/logrus to v1.9.4
2026-01-15 17:44:27 +01:00
renovate[bot]
c7e238a4f8 fix(deps): update module github.com/sirupsen/logrus to v1.9.4
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-15 16:26:41 +00:00
Miloslav Trmač
bd93940d5b Merge pull request #2786 from containers/renovate/common-image-and-storage-deps
fix(deps): update common, image, and storage deps to e7626b7
2026-01-12 18:32:21 +01:00
renovate[bot]
669e21cd77 fix(deps): update common, image, and storage deps to e7626b7
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-12 17:02:10 +00:00
Miloslav Trmač
94f776ad95 Merge pull request #2785 from containers/renovate/golang.org-x-term-0.x
fix(deps): update module golang.org/x/term to v0.39.0
2026-01-12 18:00:48 +01:00
renovate[bot]
b4516c6eea fix(deps): update module golang.org/x/term to v0.39.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-09 20:56:44 +00:00
Paul Holzinger
0c1d9730f8 Merge pull request #2783 from containers/renovate/golangci-golangci-lint-2.x
chore(deps): update dependency golangci/golangci-lint to v2.8.0
2026-01-08 14:56:53 +01:00
renovate[bot]
0c04335b21 chore(deps): update dependency golangci/golangci-lint to v2.8.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-08 13:39:39 +00:00
Paul Holzinger
b3007103d7 Merge pull request #2781 from mtrmac/retry-docs
Document the default of --retry-times
2026-01-08 14:39:05 +01:00
Miloslav Trmač
ef323fcce3 Document the default of --retry-times
Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2026-01-07 18:25:26 +01:00
Miloslav Trmač
af43514563 Merge pull request #2780 from promalert/main
chore: fix function name in comment
2026-01-07 16:17:57 +01:00
promalert
f952b7facd chore: fix function name in comment
Signed-off-by: promalert <promalert@outlook.com>
2026-01-07 15:03:46 +08:00
Miloslav Trmač
592464e7c8 Merge pull request #2714 from jlebon/pr/reject-insecure
skopeo: add `--require-signed`
2026-01-05 22:51:51 +01:00
Jonathan Lebon
40f0e16777 skopeo: add --require-signed
In bootc, we want the ability to assert that signature verification is
enforced.

Add a new top-level `--require-signed` switch. When passed, we use the
new `RequireSignatureVerification()` method to ensure that signature
verification is enforced.

Part of https://github.com/containers/skopeo/issues/1829.

Signed-off-by: Jonathan Lebon <jonathan@jlebon.com>
2026-01-05 15:02:18 -05:00
Jonathan Lebon
767d9cb005 integration/signing_test: move findFingerprint to utils_test.go
Prep for another test which will also use this.

Signed-off-by: Jonathan Lebon <jonathan@jlebon.com>
2025-12-27 22:07:41 -05:00
Lokesh Mandvekar
47e615b9a8 Merge pull request #2774 from containers/renovate/common-image-and-storage-deps
fix(deps): update common, image, and storage deps to b0f86df
2025-12-23 10:31:24 -05:00
renovate[bot]
01c33a7e4b fix(deps): update common, image, and storage deps to b0f86df
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-14 02:56:30 +00:00
Miloslav Trmač
707c470866 Merge pull request #2770 from mtrmac/common-dep
Update c/common to match #2765
2025-12-09 20:52:46 +01:00
Miloslav Trmač
7c747f8220 Update c/common to match #2765
Renovate seems to have had a difficulty with that:
> Could not determine new digest for update (go package go.podman.io/common)

for some reason, let's do it manually now, hoping that
Renovate will catch up next week.

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2025-12-09 19:57:44 +01:00
Miloslav Trmač
46b2b95d57 Merge pull request #2765 from containers/renovate/common-image-and-storage-deps
fix(deps): update common, image, and storage deps to afd10d8
2025-12-09 19:52:01 +01:00
renovate[bot]
9efaa1c010 fix(deps): update common, image, and storage deps to afd10d8
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-09 18:34:31 +00:00
Miloslav Trmač
7e659707da Merge pull request #2766 from containers/renovate/golangci-golangci-lint-2.x
chore(deps): update dependency golangci/golangci-lint to v2.7.2
2025-12-09 19:22:00 +01:00
renovate[bot]
54b4159187 chore(deps): update dependency golangci/golangci-lint to v2.7.2
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-09 17:59:12 +00:00
Miloslav Trmač
e0d4b7b8e5 Merge pull request #2768 from containers/renovate/golang.org-x-term-0.x
fix(deps): update module golang.org/x/term to v0.38.0
2025-12-09 18:57:46 +01:00
renovate[bot]
ad431f6d1c fix(deps): update module golang.org/x/term to v0.38.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-08 18:17:13 +00:00
Miloslav Trmač
2821fe75d0 Merge pull request #2763 from lsm5/packit-fix
Packit: use `post-modifications` hook to update downstream TMT plan
2025-12-05 18:03:38 +01:00
Lokesh Mandvekar
e26a4237fc Packit: use post-modifications hook to update downstream TMT plan
`prepare-files` action was interfering with spec file update which caused
https://github.com/containers/skopeo/issues/2760 .

`post-modifications` needs to be limited to the propose_downstream job or
else it will interfere with upstream PR copr builds.

Also, s/PACKIT_PROJECT_TAG/PACKIT_PROJECT_VERSION/ .

Co-authored-by: Nikola Forró <nforro@redhat.com>
Signed-off-by: Lokesh Mandvekar <lsm5@redhat.com>
2025-12-05 08:50:02 -05:00
Miloslav Trmač
ce4265f9c0 Merge pull request #2747 from lsm5/digest-redux
skopeo inspect: support for sha512 images
2025-12-04 21:33:18 +01:00
Lokesh Mandvekar
420cd29beb docs: manpage update for skopeo inspect --manifest-digest
Signed-off-by: Lokesh Mandvekar <lsm5@redhat.com>
2025-12-04 15:10:49 -05:00
Lokesh Mandvekar
f85b6db46e inspect: --manifest-digest flag
If this flag is specified, it'll display digest of that type, otherwise
it'll display the original digest.

Doesn't break any existing sha256 workflow.

Example:

1. Default
```
$ ./bin/skopeo inspect docker://docker.io/library/alpine:latest --format "Digest: {{.Digest}}"
Digest: sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412
```

2. with --manifest-digest
```
$ ./bin/skopeo inspect --manifest-digest=sha512 docker://docker.io/library/alpine:latest --format "Digest: {{.Digest}}"
Digest: sha512:5acb33fb56a7791bf0c69d5b19a1c70272148e4107be5261d57305d14e9509792bbca53e5277c456181ecfa1c20ad8427f9b8ba46868020584a819de1128dbd2
```

Signed-off-by: Lokesh Mandvekar <lsm5@redhat.com>
2025-12-04 15:10:42 -05:00
Lokesh Mandvekar
a25bf91823 vendor: container-libs commit 01833ef7b7f1d306205be7fa6fb36d0d6a6e3a33
Includes `manifest.DigestWithAlgorithm` and support for sha512 for
skopeo copy and inspect.

Signed-off-by: Lokesh Mandvekar <lsm5@redhat.com>
2025-12-04 14:09:27 -05:00
Miloslav Trmač
ecf6e2c79c Merge pull request #2764 from containers/renovate/golangci-golangci-lint-2.x
chore(deps): update dependency golangci/golangci-lint to v2.7.1
2025-12-04 20:05:07 +01:00
renovate[bot]
0291b1e001 chore(deps): update dependency golangci/golangci-lint to v2.7.1
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-04 18:09:29 +00:00
Miloslav Trmač
85dc7471fe Merge pull request #2762 from containers/renovate/github.com-spf13-cobra-1.x
fix(deps): update module github.com/spf13/cobra to v1.10.2
2025-12-04 19:09:04 +01:00
renovate[bot]
f7d8ca9876 fix(deps): update module github.com/spf13/cobra to v1.10.2
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-04 03:06:26 +00:00
Miloslav Trmač
b440fae236 Merge pull request #2761 from containers/renovate/golangci-golangci-lint-2.x
chore(deps): update dependency golangci/golangci-lint to v2.7.0
2025-12-03 22:51:02 +01:00
renovate[bot]
bad5bd046d chore(deps): update dependency golangci/golangci-lint to v2.7.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-03 20:51:09 +00:00
Paul Holzinger
53800e09e2 Merge pull request #2759 from mtrmac/1.21-bump
Bump version to 1.22.0-dev
2025-12-03 20:06:47 +01:00
Miloslav Trmač
31d50fd0f9 Bump version to 1.22.0-dev
1.21.0 was released (off an earlier commit).

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2025-12-03 19:35:25 +01:00
Miloslav Trmač
f358adffdd Merge pull request #2757 from containers/renovate/common-image-and-storage-deps
Update common, image, and storage deps to 63be353
2025-12-01 22:40:52 +01:00
renovate[bot]
bd5ec4425d Update common, image, and storage deps to 63be353
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-12-01 20:49:31 +00:00
Lokesh Mandvekar
f7e1211a41 Merge pull request #2758 from mtrmac/ostree-base-image
Try triggering an image rebuild
2025-12-01 15:48:28 -05:00
Miloslav Trmač
287045706c Try triggering an image rebuild
https://cirrus-ci.com/task/4601027732701184 shows it using
go 1.24.3, but https://quay.io/repository/coreos-assembler/fcos-buildroot/manifest/sha256:55d7510ee1b15ae4c8c503efaa463c58ce0c3d484ab7ec4fe8b211ca5a5aacf9?tab=packages
shows 1.25.4 already; are we caching an older build?

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2025-12-01 19:44:55 +01:00
Miloslav Trmač
279c831898 Merge pull request #2753 from containers/renovate/common-image-and-storage-deps
Update common, image, and storage deps to 22d50c5
2025-11-25 04:00:44 +01:00
renovate[bot]
3498d8fc77 Update common, image, and storage deps to 22d50c5
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-23 01:04:16 +00:00
Miloslav Trmač
107b1b1ed2 Merge pull request #2743 from containers/renovate/golangci-golangci-lint-2.x
Update dependency golangci/golangci-lint to v2.6.2
2025-11-19 18:28:35 +01:00
renovate[bot]
ae484462c6 Update dependency golangci/golangci-lint to v2.6.2
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-19 17:09:55 +00:00
Miloslav Trmač
733c4d6ad9 Merge pull request #2746 from lsm5/container-libs-vendor
Container libs vendor and Renovate config update
2025-11-19 18:07:42 +01:00
Lokesh Mandvekar
75bc19e334 vendor: Fetch the latest from container-libs main
Signed-off-by: Lokesh Mandvekar <lsm5@redhat.com>
2025-11-19 10:35:40 -05:00
Miloslav Trmač
c844ecb70c Merge pull request #2744 from Luap99/gofumpt
golangci-lint: enable gofumpt formatter
2025-11-18 23:35:18 +01:00
Paul Holzinger
b625905314 golangci-lint: enable gofumpt formatter
And also remove old, no longer needed validate-gofmt.sh script.
golangci-lint checks the formatting already for us.

And add a fmt make target that just runs golangci-lint fmt for easier
use.

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
2025-11-18 22:46:37 +01:00
Paul Holzinger
7182fecc79 format the code with gofumpt
Use it based on the outcome from our community discussion[1].

[1] https://github.com/containers/podman/discussions/27291

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
2025-11-18 22:46:37 +01:00
Miloslav Trmač
655f2b977b Merge pull request #2749 from lsm5/tmt-disable-eln-x64
Packit: tmp disable ELN tests
2025-11-18 22:33:39 +01:00
Lokesh Mandvekar
2a6fd74207 Packit: tmp disable ELN tests
Ref: https://github.com/containers/skopeo/issues/2748

Signed-off-by: Lokesh Mandvekar <lsm5@redhat.com>
2025-11-17 13:29:09 -05:00
Miloslav Trmač
52d1fba7a4 Merge pull request #2742 from containers/renovate/golang.org-x-term-0.x
fix(deps): update module golang.org/x/term to v0.37.0
2025-11-11 21:14:50 +01:00
renovate[bot]
4ac321f3bc fix(deps): update module golang.org/x/term to v0.37.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-11 19:31:08 +00:00
Miloslav Trmač
7c934acec9 Merge pull request #2741 from lsm5/podman-next-no-notify
Packit: Do not notify on podman-next copr build failure
2025-11-10 15:40:33 +01:00
Lokesh Mandvekar
399adc0ef8 Packit: Do not notify on podman-next copr build failure
These happen after commit to upstream and don't affect upstream.

These notifications only end up adding unnecessary noise.

Overall build failures can happen for a variety of reasons like copr
infra, outdated toolchain on some environments etc.

Signed-off-by: Lokesh Mandvekar <lsm5@redhat.com>
2025-11-10 09:13:23 -05:00
Paul Holzinger
75f2311b4c Merge pull request #2720 from mtrmac/empty-sigstore
Allow using sigstore keys with empty passphrases
2025-11-10 14:48:10 +01:00
Miloslav Trmač
b7856d6be6 Support empty sigstore passphrases, for cosign compatibility
Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2025-11-10 14:24:36 +01:00
Miloslav Trmač
db81e690a7 Make TestSharedCopyOptionsCopyOptions table-driven
Should not change (test) behavior.

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2025-11-10 14:24:29 +01:00
Paul Holzinger
9e00374ce2 Merge pull request #2740 from mtrmac/retry
Use retries in more cases of copying from external registries
2025-11-10 14:19:26 +01:00
Miloslav Trmač
fe086daee7 Use retries in more cases of copying from external registries
We added retries to other registry operations before,
these were missed for some reason.

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2025-11-10 13:54:43 +01:00
Miloslav Trmač
00de3af75f Merge pull request #2731 from lsm5/test-system-fix
Fix test-system and remove stale Makefile target
2025-11-05 20:07:55 +01:00
Lokesh Mandvekar
8181f14eaa Makefile: remove stale target
Old cruft. Isn't even expected to work. Not used anywhere.

Signed-off-by: Lokesh Mandvekar <lsm5@redhat.com>
2025-11-05 13:36:52 -05:00
Lokesh Mandvekar
85a14032cc Makefile: use hack/test-system.sh for test-system-local
This will ensure both `test-system` and `test-system-local` work.

The `test-system` target will use the skopeo binary at `./bin/skopeo`.
Setting SKOPEO_BINARY should have no effect on it.

The `test-system-local` (and `test-integration-local`) target can use
SKOPEO_BINARY at any location. This will be useful in CI where we need to test
skopeo installed by the package at /usr/bin.

Co-authored-by: Miloslav Trmač <mitr@redhat.com>
Signed-off-by: Lokesh Mandvekar <lsm5@redhat.com>
2025-11-05 13:36:43 -05:00
Lokesh Mandvekar
7ae3e0bc63 Recreate hack/test-system.sh
`hack/test-system.sh` earlier removed in commit 7e235ea contained
a storage.conf setup useful for running system tests in skopeo_cidev
container.

Getting rid of that also broke `make test-system` because the
systemtest: `copy: additional tag` started to break.

Running `make test-system-local` isn't ideal for local environments due
to creation of system-global containers with predictable names.
See: https://github.com/containers/skopeo/issues/2701#issuecomment-3415769846

So, this commit resurrects hack/test-system.sh. Followup commit will
modify Makefile targets to use this script again.

Signed-off-by: Lokesh Mandvekar <lsm5@redhat.com>
2025-11-05 08:51:04 -05:00
Miloslav Trmač
13f1418f74 Merge pull request #2737 from containers/renovate/golangci-golangci-lint-2.x
chore(deps): update dependency golangci/golangci-lint to v2.6.1
2025-11-04 19:27:42 +01:00
renovate[bot]
f3e66b8fa0 chore(deps): update dependency golangci/golangci-lint to v2.6.1
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-04 14:36:33 +00:00
Miloslav Trmač
bcfc9ce669 Merge pull request #2736 from containers/renovate/github.com-opencontainers-image-spec-digest
fix(deps): update github.com/opencontainers/image-spec digest to 26647a4
2025-11-03 17:34:29 +01:00
renovate[bot]
f399e1d0f0 fix(deps): update github.com/opencontainers/image-spec digest to 26647a4
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-01 03:32:10 +00:00
Miloslav Trmač
85598438ce Merge pull request #2734 from containers/renovate/golangci-golangci-lint-2.x
chore(deps): update dependency golangci/golangci-lint to v2.6.0
2025-10-30 21:16:20 +01:00
renovate[bot]
f23b6fb8f1 chore(deps): update dependency golangci/golangci-lint to v2.6.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-30 00:57:50 +00:00
Colin Walters
53f9612136 main: Add support for overriding HTTP User-Agent
I want this for https://github.com/bootc-dev/bootc/issues/1686
so we can distinguish pulls there.

But more generally it's can be a good idea for people writing
scripts using skopeo to set custom user agents so that registries
can more easily trace which actors are performing tasks.

Assisted-by: Claude Code
Signed-off-by: Colin Walters <walters@verbum.org>
2025-10-20 19:40:17 +02:00
Miloslav Trmač
6b2c20caef Merge pull request #2726 from containers/renovate/go.podman.io-common-0.x
fix(deps): update module go.podman.io/common to v0.66.0
2025-10-20 19:16:28 +02:00
renovate[bot]
f9d4a40261 fix(deps): update module go.podman.io/common to v0.66.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2025-10-20 18:41:44 +02:00
Miloslav Trmač
77325403b2 Merge pull request #2727 from containers/renovate/go.podman.io-image-v5-5.x
fix(deps): update module go.podman.io/image/v5 to v5.38.0
2025-10-20 18:30:40 +02:00
renovate[bot]
ff5394430c fix(deps): update module go.podman.io/image/v5 to v5.38.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 13:54:02 +00:00
Miloslav Trmač
83ca23a899 Merge pull request #2728 from containers/renovate/go.podman.io-storage-1.x
fix(deps): update module go.podman.io/storage to v1.61.0
2025-10-20 15:52:35 +02:00
renovate[bot]
7a9cea1bf1 fix(deps): update module go.podman.io/storage to v1.61.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-18 05:45:58 +00:00
Miloslav Trmač
d2a1740f0d Merge pull request #2719 from mtrmac/generate-newline
Add missing newline to the output of (skopeo generate-sigstore-key)
2025-10-14 18:20:35 +02:00
Miloslav Trmač
144e9151bd Add missing newline to the output of (skopeo generate-sigstore-key)
Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2025-10-13 20:47:47 +02:00
Miloslav Trmač
01f5061a82 Merge pull request #2722 from containers/renovate/golang.org-x-term-0.x
fix(deps): update module golang.org/x/term to v0.36.0
2025-10-09 11:17:30 +02:00
renovate[bot]
a45ceb03d3 fix(deps): update module golang.org/x/term to v0.36.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-08 17:44:41 +00:00
Miloslav Trmač
3d18794ee8 Merge pull request #2711 from mtrmac/sync-tls
Simplify tests for #2710
2025-10-02 19:39:15 +02:00
Miloslav Trmač
d287ec074a Fold the test cases of TestSyncTLSPrecedence to a single line each
... to make it much easier to compare+contrast.

Should not change (test) behavior.

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2025-10-02 17:42:49 +02:00
Miloslav Trmač
286c599025 Remove the explicit test name
... and construct it from the primary inputs instead.

The name doesn't hurt in itself, but it ~forces us to use
multi-line test data, making this larger and harder to read than
necessary.

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2025-10-02 17:42:40 +02:00
Miloslav Trmač
5699f10b40 Construct the source context from CLI instead of manually
This changes the test, we drop the two inputs that can't happen
in practice; and, instead, test the default situation of no TLS
options provided anywhere.

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2025-10-02 17:42:29 +02:00
Miloslav Trmač
6d2144e685 Use a real YAML fragment instead of constructing inputs manually
To be more clearly representative.

Should not change (test) behavior.

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2025-10-02 17:42:19 +02:00
Miloslav Trmač
1e6817a12c Merge the two TestTLSPrecedence tests
They use exactly the same body, and differ only in data
(and we are going to keep iterating on the body).

Should not change (test) behavior.

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2025-10-02 17:41:52 +02:00
Lokesh Mandvekar
fcf9dd6911 Merge pull request #2713 from containers/renovate/golangci-golangci-lint-2.x
Update dependency golangci/golangci-lint to v2.5.0
2025-10-02 10:03:53 -04:00
renovate[bot]
d80c776b87 Update dependency golangci/golangci-lint to v2.5.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-01 16:04:49 +00:00
Miloslav Trmač
d79dd05773 Merge pull request #2717 from containers/renovate/github.com-opencontainers-image-spec-digest
Update github.com/opencontainers/image-spec digest to 6519a62
2025-10-01 17:57:30 +02:00
renovate[bot]
c7a3a90716 Update github.com/opencontainers/image-spec digest to 6519a62
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-01 01:46:04 +00:00
Miloslav Trmač
b810e9eb46 Merge pull request #2710 from andya1lan/fix-sync-tls-verify-yaml
sync: honor CLI/global tls-verify unless YAML explicitly sets tls-verify
2025-09-19 18:46:58 +02:00
Andy Allan
dbd18b9728 sync: honor CLI/global tls-verify unless YAML explicitly sets tls-verify
Signed-off-by: Andy Allan <58987282+andya1lan@users.noreply.github.com>

chore: make linter happy

Signed-off-by: Andy Allan <58987282+andya1lan@users.noreply.github.com>

test(sync): refactor TLS override precedence test to table-driven

Signed-off-by: Andy Allan <58987282+andya1lan@users.noreply.github.com>

test(sync): refactor cases when YAML omitted

Signed-off-by: Andy Allan <58987282+andya1lan@users.noreply.github.com>

test(sync): readd TLS verification tests when yaml specfied

Signed-off-by: Andy Allan <58987282+andya1lan@users.noreply.github.com>

test(sync): readd TLS verification tests when yaml specfied and `incomingDaemonSkip` condition

Signed-off-by: Andy Allan <58987282+andya1lan@users.noreply.github.com>
2025-09-19 18:07:14 +08:00
Miloslav Trmač
f0f0c2c639 Merge pull request #2703 from lsm5/integration-test-binary-path
test-integration-local: use SKOPEO_BINARY if set
2025-09-17 15:06:55 +02:00
Lokesh Mandvekar
a489ea39a7 test-integration-local: use SKOPEO_BINARY if set
Else use ./bin/skopeo as the default binary.

This makes it a lot more flexible compared to just searching and using
the first skopeo in PATH. Also, allows to avoid any binary installation
to /usr/bin.

The SKOPEO_BINARY evaluation logic has been moved to a separate and
reusable Makefile target.

Co-authored-by: Miloslav Trmač <mitr@redhat.com>
Signed-off-by: Lokesh Mandvekar <lsm5@redhat.com>
2025-09-17 08:31:10 -04:00
Miloslav Trmač
96eabba0b4 Merge pull request #2645 from mtrmac/sequoia-cli
Add --sign-by-sq-fingerprint
2025-09-11 19:32:58 +02:00
Miloslav Trmač
f59e8938db Add --sign-by-sq-fingerprint and an integration test
Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2025-09-11 19:00:40 +02:00
Miloslav Trmač
9753a1a11a Also build+test with Sequoia
Relies on https://github.com/containers/automation_images/pull/411 .

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2025-09-11 19:00:27 +02:00
Miloslav Trmač
cba2164671 Update CI images
... from https://github.com/containers/automation_images/pull/416

That requires updating tests to also work with rust-podman-sequoia 0.2.0:
d41fefaa1f changed
how the error is reported.

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2025-09-11 19:00:20 +02:00
Miloslav Trmač
b258da23c2 Update conditions for signing tests
NewEphemeralSigningMechanism() may, with Sequoia, return
a mechanism which !SupportsSigning(); so, to determine that,
test with a non-ephemeral mechanism instead. (That's likely
actually faster, because we create a GNUPGHOME in these tests
anyway, so we avoid creating an deleting a separate temporary
directory.)

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2025-09-11 19:00:10 +02:00
Miloslav Trmač
99e3d917f9 Merge pull request #2705 from containers/renovate/golang.org-x-term-0.x
Update module golang.org/x/term to v0.35.0
2025-09-10 19:57:19 +02:00
renovate[bot]
86853ba4a5 Update module golang.org/x/term to v0.35.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-10 17:38:56 +00:00
Miloslav Trmač
933f797dec Merge pull request #2706 from mtrmac/go1.24
Update to Go1.24
2025-09-10 15:33:14 +02:00
Miloslav Trmač
0114569b17 Use strings.SplitSeq
Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2025-09-08 23:05:37 +02:00
Miloslav Trmač
24c2a374d6 Use bytes.SplitSeq
Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2025-09-08 23:05:30 +02:00
Miloslav Trmač
afc0c0f021 Rely on Go-maintained commit value instead of handling that ourselves
Should not change behavior

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2025-09-08 23:05:20 +02:00
Miloslav Trmač
509ed8e1a4 Update to Go 1.24
Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2025-09-08 23:05:05 +02:00
Miloslav Trmač
ac66ca51ec Merge pull request #2700 from lsm5/rpm-sequoia
Packit/RPM: build with sequoia for f43+ and couple of other changes for distro conditionals and easier testing
2025-09-08 18:55:31 +02:00
Lokesh Mandvekar
53bba51c2f RPM: build with sequoia for F43+
Signed-off-by: Lokesh Mandvekar <lsm5@redhat.com>
2025-09-08 18:38:09 +02:00
Lokesh Mandvekar
85e4414c43 RPM: Re-org distro conditionals
Signed-off-by: Lokesh Mandvekar <lsm5@redhat.com>
2025-09-08 18:38:09 +02:00
Lokesh Mandvekar
af27d1d877 Packit/RPM: Epoch and version fetching fixes
1. Packit config includes a `get-current-version` action to fetch the
   version from `version/version.go` and not from the default `git
   describe`. So, the copr rpm version will always be ahead of what's in
   the official repos.

2. The absurdly high RPM Epoch is now limited to builds from
   rhcontainerbot/podman-next copr. So, copr builds on PR jobs will use
   the default Epoch value.

These two changes will make it easier for testers to fetch the scratch copr builds from
upstream PRs and it should allow for seamless upgrades.
(official rpm -> test copr rpm for bugfix -> upgrade to new official rpm)

Signed-off-by: Lokesh Mandvekar <lsm5@redhat.com>
2025-09-08 18:38:09 +02:00
Miloslav Trmač
ee194d4c44 Merge pull request #2702 from bitoku/update-contributing.md
Update CONTRIBUTING.md
2025-09-08 18:16:10 +02:00
Ayato Tokubi
193b51364b Update CONTRIBUTING.md
Signed-off-by: Ayato Tokubi <atokubi@redhat.com>
2025-09-05 12:01:58 +00:00
Lokesh Mandvekar
d8be59c1ec Merge pull request #2699 from containers/renovate/actions-stale-10.x
[skip-ci] Update actions/stale action to v10
2025-09-04 12:33:38 -04:00
renovate[bot]
cf5b488bef [skip-ci] Update actions/stale action to v10
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-04 14:39:51 +00:00
Miloslav Trmač
b0c3f56cf8 Merge pull request #2698 from lsm5/re-enable-centos-stream
Packit: re-enable ELN and CentOS Stream jobs on PRs
2025-09-04 16:39:19 +02:00
Lokesh Mandvekar
a3cb7bf314 Packit: re-enable CentOS Stream jobs on PRs
Does not affect propose_downstream jobs.

Signed-off-by: Lokesh Mandvekar <lsm5@redhat.com>
2025-09-04 08:42:12 -04:00
Lokesh Mandvekar
8675decea0 Packit/TMT: enable ELN testing on PRs
testing-farm public ranch now provides ELN composes which enables
super-early testing of the next RHEL.

Signed-off-by: Lokesh Mandvekar <lsm5@redhat.com>
2025-09-03 15:36:59 -04:00
Miloslav Trmač
749370dd99 Merge pull request #2697 from mtrmac/monorepo-tags
Update to tagged container-libs releases
2025-09-03 18:19:34 +02:00
Miloslav Trmač
e5cf4ec75e Update to tagged container-libs releases
Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2025-09-03 17:56:58 +02:00
Miloslav Trmač
e54634f56a Merge pull request #2696 from containers/renovate/github.com-spf13-pflag-1.x
Update module github.com/spf13/pflag to v1.0.10
2025-09-03 16:48:18 +02:00
renovate[bot]
5489763ce3 Update module github.com/spf13/pflag to v1.0.10
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-02 20:41:11 +00:00
XYenon
4e452f974c Add --dest-force-compress-format option on copy
Signed-off-by: XYenon <i@xyenon.bid>
2025-09-02 18:45:10 +02:00
Lokesh Mandvekar
ef442e3696 Merge pull request #2687 from lsm5/ci-agnostic-2
Follow up on #2654
2025-09-02 09:12:03 -04:00
Lokesh Mandvekar
31ca77cea6 Packit: enable ELN
Signed-off-by: Lokesh Mandvekar <lsm5@redhat.com>
2025-09-02 08:49:05 -04:00
Lokesh Mandvekar
9558c9c013 Packit: replace files_to_sync with prepare-files
With TMT being able to fetch and run tests using git url
and ref, there's no need to sync files on every propose_downstream
Packit action.

Removing files_to_sync should be safe as we only need to sync
`.packit.yaml` which is part of Packit's default behavior already.

Other files like gating.yaml only need to exist downstream and
shouldn't need any frequent manual changes, so
we can remove those from upstream and packit file-sync too.

New setup: We use a `prepare-files` action that will operate only on
`plans/main.fmf` in downstream dist-git and update the ref with the
tag from the latest release.

Signed-off-by: Lokesh Mandvekar <lsm5@redhat.com>
2025-09-02 08:49:05 -04:00
Lokesh Mandvekar
cd978903c8 RPM: skopeo-tests only installs dependencies
The sole purpose of skopeo-tests subpackage was to make system test
files readily available for CI on bodhi updates.

Given we can reuse test config from upstream via TMT, there's no
reason to continue shipping the test files. This subpackage can be
repurposed to only install test dependencies.

Signed-off-by: Lokesh Mandvekar <lsm5@redhat.com>
2025-09-02 08:49:05 -04:00
Lokesh Mandvekar
d5881b947e TMT: additions to make local TMT runs similar to testing-farm
Signed-off-by: Lokesh Mandvekar <lsm5@redhat.com>
2025-09-02 08:49:04 -04:00
Miloslav Trmač
16897af59a Merge pull request #2693 from containers/renovate/github.com-spf13-cobra-1.x
Update module github.com/spf13/cobra to v1.10.1
2025-09-01 19:49:41 +02:00
renovate[bot]
dea3a8f16f Update module github.com/spf13/cobra to v1.10.1
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-01 16:50:56 +00:00
Miloslav Trmač
e4cb491edb Merge pull request #2692 from containers/renovate/github.com-spf13-pflag-1.x
Update module github.com/spf13/pflag to v1.0.9
2025-09-01 18:50:01 +02:00
renovate[bot]
11dedf8b4a Update module github.com/spf13/pflag to v1.0.9
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-01 16:22:21 +00:00
Miloslav Trmač
6d210dd074 Merge pull request #2691 from containers/renovate/go-github.com-ulikunitz-xz-vulnerability
Update module github.com/ulikunitz/xz to v0.5.15 [SECURITY]
2025-09-01 18:21:15 +02:00
renovate[bot]
75eb26e6d1 Update module github.com/ulikunitz/xz to v0.5.15 [SECURITY]
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-31 11:24:38 +00:00
Miloslav Trmač
1666b135d4 Merge pull request #2654 from lsm5/ci-agnostic
fixes to have test-*-local Makefile targets work
2025-08-29 20:27:29 +02:00
Lokesh Mandvekar
19f1202cd8 TMT: run systemtest via make test-system-local
Reuses Makefile logic and also prints SKOPEO_BINARY value

Signed-off-by: Lokesh Mandvekar <lsm5@redhat.com>
2025-08-29 08:46:05 -04:00
Lokesh Mandvekar
02055fb052 Makefile: inline logic for test-integration-local
Get rid of hack/test-integration.sh

Signed-off-by: Lokesh Mandvekar <lsm5@redhat.com>
2025-08-29 08:46:05 -04:00
Lokesh Mandvekar
7e235ea335 Makefile: test-system-local checks for SKOPEO_BINARY
Use inline script and get rid of hack/test-system.sh

Signed-off-by: Lokesh Mandvekar <lsm5@redhat.com>
2025-08-29 08:46:05 -04:00
Lokesh Mandvekar
bb1f057541 Makefile: validate-local depends on tools
This is required for fetching golangci-lint if it doesn't exist.

Signed-off-by: Lokesh Mandvekar <lsm5@redhat.com>
2025-08-29 08:46:04 -04:00
Miloslav Trmač
a958e86289 Merge pull request #2685 from containers/renovate/github.com-stretchr-testify-1.x
fix(deps): update module github.com/stretchr/testify to v1.11.1
2025-08-28 20:47:45 +02:00
renovate[bot]
885a4fbacd fix(deps): update module github.com/stretchr/testify to v1.11.1
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-28 18:02:15 +00:00
Miloslav Trmač
0f2339c09c Merge pull request #2688 from jankaluza/monorepo
Migrate storage, image and common to monorepo and remote go.podman.io imports
2025-08-28 20:01:32 +02:00
Jan Kaluza
d64631991b Migrate storage, image and common to monorepo and remote go.podman.io imports.
Signed-off-by: Jan Kaluza <jkaluza@redhat.com>
2025-08-28 09:21:52 +02:00
Miloslav Trmač
fdcd7c3ebc Merge pull request #2681 from mtrmac/accept-rawhide
Allow running CI from rawhide images
2025-08-25 12:57:56 +02:00
Miloslav Trmač
2451f10dd8 Allow running CI from rawhide images
We will, temporarily, want to run from rawhide images
that include the rust-podman-sequoia RPM, as well as Fedora 43
images.

We have only one skopeo_cidev though, and we don't want to build
a rawhide variant; so, allow consuming it from rawhide.

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2025-08-20 21:44:38 +02:00
Miloslav Trmač
8602d2e525 Stop using fgrep
> fgrep: warning: fgrep is obsolescent; using grep -F

Should not change behavior.

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2025-08-20 21:42:28 +02:00
Paul Holzinger
eecc6c8590 Merge pull request #2679 from mtrmac/accept-sequoia-backend
Update tests to work with the Sequoia c/image backend
2025-08-20 19:51:47 +02:00
Miloslav Trmač
0a70844b33 Update tests to work with the Sequoia c/image backend
Currently, if a key is not found, the GPG mechanism reports
> Invalid GPG signature: {$GoStructDump}
while the Sequoia one reports
> $keyFingerprint was not found

Accept both.

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
2025-08-19 23:21:34 +02:00
Miloslav Trmač
32f5d3dc9a Merge pull request #2677 from containers/renovate/golangci-golangci-lint-2.x
chore(deps): update dependency golangci/golangci-lint to v2.4.0
2025-08-18 18:51:22 +02:00
renovate[bot]
01252ab813 chore(deps): update dependency golangci/golangci-lint to v2.4.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-14 15:49:49 +00:00
Lokesh Mandvekar
c45394e511 Merge pull request #2676 from mohanboddu/community_manager
Adding mohanboddu as community manager to MAINTAINERS.md
2025-08-14 11:49:25 -04:00
Mohan Boddu
056d83e60f Adding mohanboddu as community manager to MAINTAINERS.md
Since I am a community manager for podman. I would like to add myself to the community manager role, as per https://github.com/containers/podman/blob/main/GOVERNANCE.md#maintainers-file, specifically "The MAINTAINERS.md file in the main Podman repository is used for project-spanning roles, including Core Maintainer and Community Manager."

https://github.com/containers/podman/blob/main/MAINTAINERS.md

Signed-off-by: Mohan Boddu <mboddu@redhat.com>
2025-08-13 15:18:28 -04:00
Miloslav Trmač
60b95b1be1 Merge pull request #2673 from containers/renovate/golang.org-x-term-0.x
fix(deps): update module golang.org/x/term to v0.34.0
2025-08-07 21:45:43 +02:00
renovate[bot]
2d34849c79 fix(deps): update module golang.org/x/term to v0.34.0
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-07 18:04:08 +00:00
Miloslav Trmač
d983e39b88 Merge pull request #2672 from containers/renovate/github.com-containers-common-0.x
fix(deps): update module github.com/containers/common to v0.64.1
2025-08-05 22:18:30 +02:00
renovate[bot]
5538ce8c5f fix(deps): update module github.com/containers/common to v0.64.1
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-05 17:11:40 +00:00
Miloslav Trmač
5db9ff4ad8 Merge pull request #2671 from containers/renovate/github.com-containers-image-v5-5.x
fix(deps): update module github.com/containers/image/v5 to v5.36.1
2025-08-04 23:13:55 +02:00
renovate[bot]
12340b3e7e fix(deps): update module github.com/containers/image/v5 to v5.36.1
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-04 20:36:01 +00:00
Miloslav Trmač
ddf1ea2844 Merge pull request #2670 from containers/renovate/github.com-containers-storage-1.x
fix(deps): update module github.com/containers/storage to v1.59.1
2025-08-04 21:30:57 +02:00
renovate[bot]
94ef22e9e7 fix(deps): update module github.com/containers/storage to v1.59.1
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-04 16:33:39 +00:00
Miloslav Trmač
a7a0fc47b6 Merge pull request #2668 from containers/renovate/golangci-golangci-lint-2.x
chore(deps): update dependency golangci/golangci-lint to v2.3.1
2025-08-04 13:41:57 +02:00
renovate[bot]
f9d10b3c9c chore(deps): update dependency golangci/golangci-lint to v2.3.1
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-03 00:51:58 +00:00
Miloslav Trmač
47aa5f526b Merge pull request #2666 from TomSweeneyRedHat/dev/tsweeney/v1.20.0
Bump to v1.20.0 then v1.21.0-dev
2025-08-02 14:07:30 +02:00
tomsweeneyredhat
af9f6d73e0 Bump to v1.21.0-dev
Bump main back to the next dev version.

Signed-off-by: tomsweeneyredhat <tsweeney@redhat.com>
2025-08-01 15:00:17 -04:00
1960 changed files with 55752 additions and 128928 deletions

View File

@@ -21,8 +21,14 @@ env:
SCRIPT_BASE: "./contrib/cirrus"
# Google-cloud VM Images
IMAGE_SUFFIX: "c20250721t181111z-f42f41d13"
# If you are updating IMAGE_SUFFIX: We are currently using rawhide for
# the containers_image_sequoia tests because the rust-podman-sequoia
# package is not available in earlier releases; once we update to a future
# Fedora release (or if the package is backported), switch back from Rawhide
# to the latest Fedora release.
IMAGE_SUFFIX: "c20250910t092246z-f42f41d13"
FEDORA_CACHE_IMAGE_NAME: "fedora-${IMAGE_SUFFIX}"
RAWHIDE_CACHE_IMAGE_NAME: "rawhide-${IMAGE_SUFFIX}"
# Container FQIN's
FEDORA_CONTAINER_FQIN: "quay.io/libpod/fedora_podman:${IMAGE_SUFFIX}"
@@ -151,7 +157,7 @@ ostree-rs-ext_task:
dockerfile: contrib/cirrus/ostree_ext.dockerfile
docker_arguments: # required build-args
BASE_FQIN: quay.io/coreos-assembler/fcos-buildroot:testing-devel
CIRRUS_IMAGE_VERSION: 3
CIRRUS_IMAGE_VERSION: 4
env:
EXT_REPO_NAME: ostree-rs-ext
EXT_REPO_HOME: $CIRRUS_WORKING_DIR/../$EXT_REPO_NAME
@@ -190,14 +196,21 @@ test_skopeo_task:
# Required to be 200gig, do not modify - has i/o performance impact
# according to gcloud CLI tool warning messages.
disk: 200
image_name: ${FEDORA_CACHE_IMAGE_NAME}
image_name: ${VM_IMAGE_NAME}
matrix:
- name: "Skopeo Test" # N/B: Name ref. by hack/get_fqin.sh
env:
BUILDTAGS: ''
VM_IMAGE_NAME: ${FEDORA_CACHE_IMAGE_NAME}
- name: "Skopeo Test w/ opengpg"
env:
BUILDTAGS: *withopengpg
VM_IMAGE_NAME: ${FEDORA_CACHE_IMAGE_NAME}
- name: "Skopeo test w/ Sequoia (currently Rawhide)"
env:
BUILDTAGS: 'containers_image_sequoia'
# If you are removing the use of rawhide, also remove the VM_IMAGE_NAME condition from runner.sh .
VM_IMAGE_NAME: ${RAWHIDE_CACHE_IMAGE_NAME}
setup_script: >-
"${GOSRC}/${SCRIPT_BASE}/runner.sh" setup
vendor_script: >-
@@ -226,6 +239,7 @@ meta_task:
# Space-separated list of images used by this repository state
IMGNAMES: |
${FEDORA_CACHE_IMAGE_NAME}
${RAWHIDE_CACHE_IMAGE_NAME}
build-push-${IMAGE_SUFFIX}
BUILDID: "${CIRRUS_BUILD_ID}"
REPOREF: "${CIRRUS_REPO_NAME}"

View File

@@ -17,7 +17,7 @@ jobs:
pull-requests: write # for actions/stale to close stale PRs
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
- uses: actions/stale@v10
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'A friendly reminder that this issue had no activity for 30 days.'

View File

@@ -1,4 +1,9 @@
version: "2"
formatters:
enable:
- gofumpt
linters:
settings:
staticcheck:

View File

@@ -9,27 +9,6 @@
downstream_package_name: skopeo
upstream_tag_template: v{version}
# These files get synced from upstream to downstream (Fedora / CentOS Stream) on every
# propose-downstream job. This is done so tests maintained upstream can be run
# downstream in Zuul CI and Bodhi.
# Ref: https://packit.dev/docs/configuration#files_to_sync
files_to_sync:
- src: rpm/gating.yaml
dest: gating.yaml
delete: true
- src: plans/
dest: plans/
delete: true
mkpath: true
- src: systemtest/tmt/
dest: test/tmt/
delete: true
mkpath: true
- src: .fmf/
dest: .fmf/
delete: true
- .packit.yaml
packages:
skopeo-fedora:
pkg_tool: fedpkg
@@ -40,6 +19,10 @@ packages:
skopeo-eln:
specfile_path: rpm/skopeo.spec
# https://packit.dev/docs/configuration/actions
actions:
get-current-version: bash -c 'grep "^const Version" version/version.go | cut -f2 -d\" | tr \- \~'
srpm_build_deps:
- make
@@ -58,12 +41,11 @@ jobs:
# https://packit.dev/posts/openscanhub-prototype
osh_diff_scan_after_copr_build: false
# Ignore until golang is updated in distro buildroot to go 1.23.3+
- job: copr_build
trigger: ignore
trigger: pull_request
packages: [skopeo-eln]
notifications: *copr_build_failure_notification
targets:
targets: &eln_copr_targets
fedora-eln-x86_64:
additional_repos:
- "https://kojipkgs.fedoraproject.org/repos/eln-build/latest/x86_64/"
@@ -72,9 +54,8 @@ jobs:
- "https://kojipkgs.fedoraproject.org/repos/eln-build/latest/aarch64/"
enable_net: true
# Ignore until golang is updated in distro buildroot to go 1.23.3+
- job: copr_build
trigger: ignore
trigger: pull_request
packages: [skopeo-centos]
notifications: *copr_build_failure_notification
targets: &centos_copr_targets
@@ -88,9 +69,6 @@ jobs:
- job: copr_build
trigger: commit
packages: [skopeo-fedora]
notifications:
failure_comment:
message: "podman-next COPR build failed. @containers/packit-build please check."
branch: main
owner: rhcontainerbot
project: podman-next
@@ -110,10 +88,22 @@ jobs:
- type: repository-file
id: https://copr.fedorainfracloud.org/coprs/rhcontainerbot/podman-next/repo/fedora-$releasever/rhcontainerbot-podman-next-fedora-$releasever.repo
# Tests on CentOS Stream for main branch
# Ignore until golang is updated in distro buildroot to go 1.23.3+
# Tests on ELN for main branch
- job: tests
# FIXME: https://github.com/containers/skopeo/issues/2748
trigger: ignore
packages: [skopeo-eln]
notifications: *test_failure_notification
targets: *eln_copr_targets
tf_extra_params:
environments:
- artifacts:
- type: repository-file
id: https://copr.fedorainfracloud.org/coprs/rhcontainerbot/podman-next/repo/fedora-eln/rhcontainerbot-podman-next-fedora-eln.repo
# Tests on CentOS Stream for main branch
- job: tests
trigger: pull_request
packages: [skopeo-centos]
notifications: *test_failure_notification
targets: *centos_copr_targets
@@ -130,6 +120,9 @@ jobs:
update_release: false
dist_git_branches: &fedora_targets
- fedora-all
actions:
post-modifications: >-
bash -c "sed -i 's/^\(\s*\)ref: .*/\1ref: \"v${PACKIT_PROJECT_VERSION}\"/' ${PACKIT_DOWNSTREAM_REPO}/plans/main.fmf"
# Sync to CentOS Stream
# FIXME: Switch trigger whenever we're ready to update CentOS Stream via

View File

@@ -129,19 +129,19 @@ In order to update an existing dependency:
- use `go get -d -u path/to/dep@version` to update the relevant dependency line in `go.mod`
- run `make vendor`
When new PRs for [containers/image](https://github.com/containers/image) break `skopeo` (i.e. `containers/image` tests fail in `make test-skopeo`):
When new PRs for [containers/container-libs](https://github.com/containers/container-libs) break `skopeo` (i.e. `containers/container-libs` tests fail in `make test-skopeo`):
- create out a new branch in your `skopeo` checkout and switch to it
- find out the version of `containers/image` you want to use and note its commit ID. You might also want to use a fork of `containers/image`, in that case note its repo
- use `go get -d github.com/$REPO/image/v5@$COMMIT_ID` to download the right version. The command will fetch the dependency and then fail because of a conflict in `go.mod`, this is expected. Note the pseudo-version (eg. `v5.13.1-0.20210707123201-50afbf0a326`)
- use `go mod edit -replace=github.com/containers/image/v5=github.com/$REPO/image/v5@$PSEUDO_VERSION` to add a replacement line to `go.mod` (e.g. `replace github.com/containers/image/v5 => github.com/moio/image/v5 v5.13.1-0.20210707123201-50afbf0a3262`)
- find out the version of `containers/container-libs` you want to use and note its commit ID. You might also want to use a fork of `containers/container-libs`, in that case note its repo
- use `go get -d github.com/$REPO/container-libs/image/v5@$COMMIT_ID` to download the right version. The command will fetch the dependency and then fail because of a conflict in `go.mod`, this is expected. Note the pseudo-version (eg. `v5.13.1-0.20210707123201-50afbf0a326`)
- use `go mod edit -replace=go.podman.io/image/v5=github.com/$REPO/container-libs/image/v5@$PSEUDO_VERSION` to add a replacement line to `go.mod` (e.g. `replace go.podman.io/image/v5 => github.com/moio/container-libs/image/v5 v5.13.1-0.20210707123201-50afbf0a3262`)
- run `make vendor`
- make any other necessary changes in the skopeo repo (e.g. add other dependencies now required by `containers/image`, or update skopeo for changed `containers/image` API)
- make any other necessary changes in the skopeo repo (e.g. add other dependencies now required by `containers/container-libs`, or update skopeo for changed `containers/container-libs` API)
- optionally add new integration tests to the skopeo repo
- submit the resulting branch as a skopeo PR, marked “DO NOT MERGE”
- iterate until tests pass and the PR is reviewed
- then the original `containers/image` PR can be merged, disregarding its `make test-skopeo` failure
- as soon as possible after that, in the skopeo PR, use `go mod edit -dropreplace=github.com/containers/image` to remove the `replace` line in `go.mod`
- then the original `containers/container-libs` PR can be merged, disregarding its `make test-skopeo` failure
- as soon as possible after that, in the skopeo PR, use `go mod edit -dropreplace=go.podman.io/image/v5` to remove the `replace` line in `go.mod`
- run `make vendor`
- update the skopeo PR with the result, drop the “DO NOT MERGE” marking
- after tests complete successfully again, merge the skopeo PR

View File

@@ -13,6 +13,7 @@ describes the project's governance and the Project Roles used below.
| Paul Holzinger | [Luap99](https://github.com/Luap99) | Core Maintainer | [Red Hat](https://github.com/RedHatOfficial) |
| Giuseppe Scrivano | [giuseppe](https://github.com/giuseppe) | Core Maintainer | [Red Hat](https://github.com/RedHatOfficial) |
| Miloslav Trmač | [mtrmac](https://github.com/mtrmac) | Core Maintainer | [Red Hat](https://github.com/RedHatOfficial) |
| Mohan Boddu | [mohanboddu](https://github.com/mohanboddu) | Community Manager | [Red Hat](https://github.com/RedHatOfficial) |
| Neil Smith | [actionmancan](https://github.com/actionmancan) | Community Manager | [Red Hat](https://github.com/RedHatOfficial) |
| Tom Sweeney | [TomSweeneyRedHat](https://github.com/TomSweeneyRedHat/) | Maintainer and Community Manager | [Red Hat](https://github.com/RedHatOfficial) |
| Lokesh Mandvekar | [lsm5](https://github.com/lsm5) | Maintainer | [Red Hat](https://github.com/RedHatOfficial) |

View File

@@ -24,10 +24,12 @@ GOBIN := $(shell $(GO) env GOBIN)
GOOS ?= $(shell go env GOOS)
GOARCH ?= $(shell go env GOARCH)
SEQUOIA_SONAME_DIR =
# N/B: This value is managed by Renovate, manual changes are
# possible, as long as they don't disturb the formatting
# (i.e. DO NOT ADD A 'v' prefix!)
GOLANGCI_LINT_VERSION := 2.3.0
GOLANGCI_LINT_VERSION := 2.8.0
ifeq ($(GOBIN),)
GOBIN := $(GOPATH)/bin
@@ -81,10 +83,8 @@ endif
CONTAINER_GOSRC = /src/github.com/containers/skopeo
CONTAINER_RUN ?= $(CONTAINER_CMD) --security-opt label=disable -v $(CURDIR):$(CONTAINER_GOSRC) -w $(CONTAINER_GOSRC) $(SKOPEO_CIDEV_CONTAINER_FQIN)
GIT_COMMIT := $(shell GIT_CEILING_DIRECTORIES=$$(cd ..; pwd) git rev-parse HEAD 2> /dev/null || true)
EXTRA_LDFLAGS ?=
SKOPEO_LDFLAGS := -ldflags '-X main.gitCommit=${GIT_COMMIT} $(EXTRA_LDFLAGS)'
SKOPEO_LDFLAGS := -ldflags '-X go.podman.io/image/v5/signature/internal/sequoia.sequoiaLibraryDir=$(SEQUOIA_SONAME_DIR) $(EXTRA_LDFLAGS)'
MANPAGES_MD = $(wildcard docs/*.md)
MANPAGES ?= $(MANPAGES_MD:%.md=%)
@@ -201,10 +201,19 @@ test-integration:
$(MAKE) test-integration-local
# Intended for CI, assumed to be running in quay.io/libpod/skopeo_cidev container.
test-integration-local: bin/skopeo
# Helper target to set up SKOPEO_BINARY variable for local test targets
# SKOPEO_BINARY only takes effect on `test-integration-local` and
# `test-system-local` targets. It's not propagated into the container used for `test-integration` and
# `test-system`. These targets will (build and) use skopeo binary at
# ./bin/skopeo.
.eval-skopeo-binary: $(if $(SKOPEO_BINARY),,bin/skopeo)
$(eval SKOPEO_BINARY := $(or $(SKOPEO_BINARY),./bin/skopeo))
@echo "Testing with $(SKOPEO_BINARY) ..."
# Primarily intended for CI.
test-integration-local: .eval-skopeo-binary
hack/warn-destructive-tests.sh
hack/test-integration.sh $(SKOPEO_LDFLAGS) $(TESTFLAGS)
cd ./integration && SKOPEO_BINARY="$(abspath $(SKOPEO_BINARY))" $(GO) test $(SKOPEO_LDFLAGS) $(TESTFLAGS) $(if $(BUILDTAGS),-tags "$(BUILDTAGS)")
# complicated set of options needed to run podman-in-podman
test-system:
@@ -218,10 +227,10 @@ test-system:
$(CONTAINER_RUNTIME) unshare rm -rf $$DTEMP; # This probably doesn't work with Docker, oh well, better than nothing... \
exit $$rc
# Intended for CI, assumed to already be running in quay.io/libpod/skopeo_cidev container.
test-system-local: bin/skopeo
# Primarily intended for CI.
test-system-local: .eval-skopeo-binary
hack/warn-destructive-tests.sh
hack/test-system.sh SKOPEO_LDFLAGS="$(SKOPEO_LDFLAGS)" BUILDTAGS="$(BUILDTAGS)"
hack/test-system.sh
test-unit:
# Just call (make test unit-local) here instead of worrying about environment differences
@@ -233,10 +242,13 @@ validate:
# This target is only intended for development, e.g. executing it from an IDE. Use (make test) for CI or pre-release testing.
test-all-local: validate-local validate-docs test-unit-local
.PHONY: fmt
fmt: tools
$(GOBIN)/golangci-lint fmt
.PHONY: validate-local
validate-local:
validate-local: tools
hack/validate-git-marks.sh
hack/validate-gofmt.sh
$(GOBIN)/golangci-lint run --build-tags "${BUILDTAGS}"
# An extra run with --tests=false allows detecting code unused outside of tests;
# ideally the linter should be able to find this automatically.
@@ -251,7 +263,7 @@ validate-docs: bin/skopeo
hack/xref-helpmsgs-manpages
test-unit-local:
$(GO) test -tags "$(BUILDTAGS)" $$($(GO) list -tags "$(BUILDTAGS)" -e ./... | grep -v '^github\.com/containers/skopeo/\(integration\|vendor/.*\)$$')
$(GO) test $(SKOPEO_LDFLAGS) -tags "$(BUILDTAGS)" $$($(GO) list -tags "$(BUILDTAGS)" -e ./... | grep -v '^github\.com/containers/skopeo/\(integration\|vendor/.*\)$$')
vendor:
$(GO) mod tidy
@@ -260,8 +272,3 @@ vendor:
vendor-in-container:
podman run --privileged --rm --env HOME=/root -v $(CURDIR):/src -w /src golang $(MAKE) vendor
# CAUTION: This is not a replacement for RPMs provided by your distro.
# Only intended to build and test the latest unreleased changes.
rpm:
rpkg local

View File

@@ -1,16 +1,17 @@
package main
import (
"github.com/containers/image/v5/directory"
"github.com/containers/image/v5/docker"
dockerArchive "github.com/containers/image/v5/docker/archive"
ociArchive "github.com/containers/image/v5/oci/archive"
oci "github.com/containers/image/v5/oci/layout"
"github.com/containers/image/v5/sif"
"github.com/containers/image/v5/tarball"
"github.com/containers/image/v5/transports"
"github.com/spf13/cobra"
"strings"
"github.com/spf13/cobra"
"go.podman.io/image/v5/directory"
"go.podman.io/image/v5/docker"
dockerArchive "go.podman.io/image/v5/docker/archive"
ociArchive "go.podman.io/image/v5/oci/archive"
oci "go.podman.io/image/v5/oci/layout"
"go.podman.io/image/v5/sif"
"go.podman.io/image/v5/tarball"
"go.podman.io/image/v5/transports"
)
func autocompleteImageNames(cmd *cobra.Command, args []string, toComplete string) ([]cobra.Completion, cobra.ShellCompDirective) {

View File

@@ -7,16 +7,16 @@ import (
"os"
"strings"
commonFlag "github.com/containers/common/pkg/flag"
"github.com/containers/common/pkg/retry"
"github.com/containers/image/v5/copy"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/transports"
"github.com/containers/image/v5/transports/alltransports"
encconfig "github.com/containers/ocicrypt/config"
enchelpers "github.com/containers/ocicrypt/helpers"
"github.com/spf13/cobra"
commonFlag "go.podman.io/common/pkg/flag"
"go.podman.io/common/pkg/retry"
"go.podman.io/image/v5/copy"
"go.podman.io/image/v5/docker/reference"
"go.podman.io/image/v5/manifest"
"go.podman.io/image/v5/transports"
"go.podman.io/image/v5/transports/alltransports"
)
type copyOptions struct {
@@ -45,7 +45,8 @@ func copyCmd(global *globalOptions) *cobra.Command {
destFlags, destOpts := imageDestFlags(global, sharedOpts, deprecatedTLSVerifyOpt, "dest-", "dcreds")
retryFlags, retryOpts := retryFlags()
copyFlags, copyOpts := sharedCopyFlags()
opts := copyOptions{global: global,
opts := copyOptions{
global: global,
deprecatedTLSVerify: deprecatedTLSVerifyOpt,
srcImage: srcOpts,
destImage: destOpts,
@@ -239,6 +240,7 @@ func (opts *copyOptions) run(args []string, stdout io.Writer) (retErr error) {
copyOpts.OciEncryptLayers = encLayers
copyOpts.OciEncryptConfig = encConfig
copyOpts.MaxParallelDownloads = opts.imageParallelCopies
copyOpts.ForceCompressionFormat = opts.destImage.forceCompressionFormat
return retry.IfNecessary(ctx, func() error {
manifestBytes, err := copy.Image(ctx, policyContext, destRef, srcRef, copyOpts)
@@ -250,7 +252,7 @@ func (opts *copyOptions) run(args []string, stdout io.Writer) (retErr error) {
if err != nil {
return err
}
if err = os.WriteFile(opts.digestFile, []byte(manifestDigest.String()), 0644); err != nil {
if err = os.WriteFile(opts.digestFile, []byte(manifestDigest.String()), 0o644); err != nil {
return fmt.Errorf("Failed to write digest to file %q: %w", opts.digestFile, err)
}
}

View File

@@ -6,10 +6,10 @@ import (
"io"
"strings"
"github.com/containers/common/pkg/retry"
"github.com/containers/image/v5/transports"
"github.com/containers/image/v5/transports/alltransports"
"github.com/spf13/cobra"
"go.podman.io/common/pkg/retry"
"go.podman.io/image/v5/transports"
"go.podman.io/image/v5/transports/alltransports"
)
type deleteOptions struct {

View File

@@ -7,9 +7,9 @@ import (
"io/fs"
"os"
"github.com/containers/image/v5/pkg/cli"
"github.com/containers/image/v5/signature/sigstore"
"github.com/spf13/cobra"
"go.podman.io/image/v5/pkg/cli"
"go.podman.io/image/v5/signature/sigstore"
)
type generateSigstoreKeyOptions struct {
@@ -79,12 +79,12 @@ func (opts *generateSigstoreKeyOptions) run(args []string, stdout io.Writer) err
return fmt.Errorf("Error generating key pair: %w", err)
}
if err := os.WriteFile(privateKeyPath, keys.PrivateKey, 0600); err != nil {
if err := os.WriteFile(privateKeyPath, keys.PrivateKey, 0o600); err != nil {
return fmt.Errorf("Error writing private key to %q: %w", privateKeyPath, err)
}
if err := os.WriteFile(pubKeyPath, keys.PublicKey, 0644); err != nil {
if err := os.WriteFile(pubKeyPath, keys.PublicKey, 0o644); err != nil {
return fmt.Errorf("Error writing private key to %q: %w", pubKeyPath, err)
}
fmt.Fprintf(stdout, "Key written to %q and %q", privateKeyPath, pubKeyPath)
fmt.Fprintf(stdout, "Key written to %q and %q\n", privateKeyPath, pubKeyPath)
return nil
}

View File

@@ -24,7 +24,7 @@ func TestGenerateSigstoreKey(t *testing.T) {
for _, suffix := range outputSuffixes {
dir := t.TempDir()
prefix := filepath.Join(dir, "prefix")
err := os.WriteFile(prefix+suffix, []byte{}, 0600)
err := os.WriteFile(prefix+suffix, []byte{}, 0o600)
require.NoError(t, err)
out, err := runSkopeo("generate-sigstore-key",
"--output-prefix", prefix, "--passphrase-file", "/dev/null",
@@ -37,7 +37,7 @@ func TestGenerateSigstoreKey(t *testing.T) {
for _, suffix := range outputSuffixes {
dir := t.TempDir()
nonDirectory := filepath.Join(dir, "nondirectory")
err := os.WriteFile(nonDirectory, []byte{}, 0600)
err := os.WriteFile(nonDirectory, []byte{}, 0o600)
require.NoError(t, err)
prefix := filepath.Join(dir, "prefix")
err = os.Symlink(filepath.Join(nonDirectory, "unaccessible"), prefix+suffix)
@@ -66,7 +66,7 @@ func TestGenerateSigstoreKey(t *testing.T) {
dir := t.TempDir()
prefix := filepath.Join(dir, "prefix")
passphraseFile := filepath.Join(dir, "passphrase")
err = os.WriteFile(passphraseFile, []byte("some passphrase"), 0600)
err = os.WriteFile(passphraseFile, []byte("some passphrase"), 0o600)
require.NoError(t, err)
out, err = runSkopeo("generate-sigstore-key",
"--output-prefix", prefix, "--passphrase-file", passphraseFile,
@@ -75,5 +75,4 @@ func TestGenerateSigstoreKey(t *testing.T) {
for _, suffix := range outputSuffixes {
assert.Contains(t, out, prefix+suffix)
}
}

View File

@@ -7,28 +7,30 @@ import (
"io"
"strings"
"github.com/containers/common/pkg/report"
"github.com/containers/common/pkg/retry"
"github.com/containers/image/v5/docker"
"github.com/containers/image/v5/image"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/transports"
"github.com/containers/image/v5/types"
"github.com/containers/skopeo/cmd/skopeo/inspect"
"github.com/docker/distribution/registry/api/errcode"
"github.com/opencontainers/go-digest"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"go.podman.io/common/pkg/report"
"go.podman.io/common/pkg/retry"
"go.podman.io/image/v5/docker"
"go.podman.io/image/v5/image"
"go.podman.io/image/v5/manifest"
"go.podman.io/image/v5/transports"
"go.podman.io/image/v5/types"
)
type inspectOptions struct {
global *globalOptions
image *imageOptions
retryOpts *retry.Options
format string
raw bool // Output the raw manifest instead of parsing information about the image
config bool // Output the raw config blob instead of parsing information about the image
doNotListTags bool // Do not list all tags available in the same repository
global *globalOptions
image *imageOptions
retryOpts *retry.Options
format string
raw bool // Output the raw manifest instead of parsing information about the image
config bool // Output the raw config blob instead of parsing information about the image
doNotListTags bool // Do not list all tags available in the same repository
manifestDigest digest.Algorithm // Algorithm to use for computing manifest digest
}
func inspectCmd(global *globalOptions) *cobra.Command {
@@ -64,6 +66,7 @@ skopeo inspect --format "Name: {{.Name}} Digest: {{.Digest}}" docker://registry.
flags.BoolVar(&opts.config, "config", false, "output configuration")
flags.StringVarP(&opts.format, "format", "f", "", "Format the output to a Go template")
flags.BoolVarP(&opts.doNotListTags, "no-tags", "n", false, "Do not list the available tags from the repository in the output")
flags.Var(newAlgorithmValue(&opts.manifestDigest), "manifest-digest", "Algorithm to use for computing manifest digest (sha256, sha512); defaults to algorithm used in config digest")
return cmd
}
@@ -176,7 +179,7 @@ func (opts *inspectOptions) run(args []string, stdout io.Writer) (retErr error)
LayersData: imgInspect.LayersData,
Env: imgInspect.Env,
}
outputData.Digest, err = manifest.Digest(rawManifest)
outputData.Digest, err = manifestDigestFromManifest(rawManifest, img, opts.manifestDigest)
if err != nil {
return fmt.Errorf("Error computing manifest digest: %w", err)
}
@@ -235,3 +238,48 @@ func (opts *inspectOptions) writeOutput(stdout io.Writer, data any) error {
defer rpt.Flush()
return rpt.Execute([]any{data})
}
func manifestDigestFromManifest(manifestBlob []byte, img types.Image, userAlgorithm digest.Algorithm) (digest.Digest, error) {
if userAlgorithm != "" {
if !userAlgorithm.Available() {
return "", fmt.Errorf("digest algorithm %q is not available", userAlgorithm)
}
return manifest.DigestWithAlgorithm(manifestBlob, userAlgorithm)
}
configInfo := img.ConfigInfo()
if configInfo.Digest != "" {
alg := configInfo.Digest.Algorithm()
if !alg.Available() {
return "", fmt.Errorf("config digest algorithm %q is not available", alg)
}
return manifest.DigestWithAlgorithm(manifestBlob, alg)
}
return manifest.Digest(manifestBlob)
}
type algorithmValue digest.Algorithm
func newAlgorithmValue(alg *digest.Algorithm) *algorithmValue {
return (*algorithmValue)(alg)
}
func (a *algorithmValue) Set(value string) error {
algorithm := digest.Algorithm(value)
*a = algorithmValue(algorithm)
if algorithm == "" {
*a = algorithmValue(digest.Canonical)
}
return nil
}
func (a *algorithmValue) String() string {
return digest.Algorithm(*a).String()
}
func (a *algorithmValue) Type() string {
return "algorithm"
}

View File

@@ -3,8 +3,8 @@ package inspect
import (
"time"
"github.com/containers/image/v5/types"
digest "github.com/opencontainers/go-digest"
"go.podman.io/image/v5/types"
)
// Output is the output format of (skopeo inspect),

View File

@@ -7,13 +7,13 @@ import (
"os"
"strings"
"github.com/containers/common/pkg/retry"
"github.com/containers/image/v5/directory"
"github.com/containers/image/v5/image"
"github.com/containers/image/v5/pkg/blobinfocache"
"github.com/containers/image/v5/types"
"github.com/opencontainers/go-digest"
"github.com/spf13/cobra"
"go.podman.io/common/pkg/retry"
"go.podman.io/image/v5/directory"
"go.podman.io/image/v5/image"
"go.podman.io/image/v5/pkg/blobinfocache"
"go.podman.io/image/v5/types"
)
type layersOptions struct {

View File

@@ -10,13 +10,13 @@ import (
"slices"
"strings"
"github.com/containers/common/pkg/retry"
"github.com/containers/image/v5/docker"
"github.com/containers/image/v5/docker/archive"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types"
"github.com/spf13/cobra"
"go.podman.io/common/pkg/retry"
"go.podman.io/image/v5/docker"
"go.podman.io/image/v5/docker/archive"
"go.podman.io/image/v5/docker/reference"
"go.podman.io/image/v5/transports/alltransports"
"go.podman.io/image/v5/types"
)
// tagListOutput is the output format of (skopeo list-tags), primarily so that we can format it with a simple json.MarshalIndent.

View File

@@ -3,16 +3,16 @@ package main
import (
"testing"
"github.com/containers/image/v5/transports/alltransports"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.podman.io/image/v5/transports/alltransports"
)
// Tests the kinds of inputs allowed and expected to the command
func TestDockerRepositoryReferenceParser(t *testing.T) {
for _, test := range [][]string{
{"docker://myhost.com:1000/nginx"}, //no tag
{"docker://myhost.com/nginx"}, //no port or tag
{"docker://myhost.com:1000/nginx"}, // no tag
{"docker://myhost.com/nginx"}, // no port or tag
{"docker://somehost.com"}, // Valid default expansion
{"docker://nginx"}, // Valid default expansion
} {
@@ -31,8 +31,8 @@ func TestDockerRepositoryReferenceParser(t *testing.T) {
{"docker-daemon:myhost.com/someimage"},
{"docker://myhost.com:1000/nginx:foobar:foobar"}, // Invalid repository ref
{"docker://somehost.com:5000/"}, // no repo
{"docker://myhost.com:1000/nginx:latest"}, //tag not allowed
{"docker://myhost.com:1000/nginx@sha256:abcdef1234567890"}, //digest not allowed
{"docker://myhost.com:1000/nginx:latest"}, // tag not allowed
{"docker://myhost.com:1000/nginx@sha256:abcdef1234567890"}, // digest not allowed
} {
_, err := parseDockerRepositoryReference(test[0])
assert.Error(t, err, test[0])
@@ -41,8 +41,8 @@ func TestDockerRepositoryReferenceParser(t *testing.T) {
func TestDockerRepositoryReferenceParserDrift(t *testing.T) {
for _, test := range [][]string{
{"docker://myhost.com:1000/nginx", "myhost.com:1000/nginx"}, //no tag
{"docker://myhost.com/nginx", "myhost.com/nginx"}, //no port or tag
{"docker://myhost.com:1000/nginx", "myhost.com:1000/nginx"}, // no tag
{"docker://myhost.com/nginx", "myhost.com/nginx"}, // no port or tag
{"docker://somehost.com", "docker.io/library/somehost.com"}, // Valid default expansion
{"docker://nginx", "docker.io/library/nginx"}, // Valid default expansion
} {

View File

@@ -4,10 +4,10 @@ import (
"io"
"os"
"github.com/containers/common/pkg/auth"
commonFlag "github.com/containers/common/pkg/flag"
"github.com/containers/image/v5/types"
"github.com/spf13/cobra"
"go.podman.io/common/pkg/auth"
commonFlag "go.podman.io/common/pkg/flag"
"go.podman.io/image/v5/types"
)
type loginOptions struct {

View File

@@ -3,10 +3,10 @@ package main
import (
"io"
"github.com/containers/common/pkg/auth"
commonFlag "github.com/containers/common/pkg/flag"
"github.com/containers/image/v5/types"
"github.com/spf13/cobra"
"go.podman.io/common/pkg/auth"
commonFlag "go.podman.io/common/pkg/flag"
"go.podman.io/image/v5/types"
)
type logoutOptions struct {

View File

@@ -3,22 +3,19 @@ package main
import (
"context"
"fmt"
"runtime/debug"
"strings"
"time"
commonFlag "github.com/containers/common/pkg/flag"
"github.com/containers/image/v5/signature"
"github.com/containers/image/v5/types"
"github.com/containers/skopeo/version"
"github.com/containers/storage/pkg/reexec"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
commonFlag "go.podman.io/common/pkg/flag"
"go.podman.io/image/v5/signature"
"go.podman.io/image/v5/types"
"go.podman.io/storage/pkg/reexec"
)
// gitCommit will be the hash that the binary was built from
// and will be populated by the Makefile
var gitCommit = ""
var defaultUserAgent = "skopeo/" + version.Version
type globalOptions struct {
@@ -33,6 +30,8 @@ type globalOptions struct {
commandTimeout time.Duration // Timeout for the command execution
registriesConfPath string // Path to the "registries.conf" file
tmpDir string // Path to use for big temporary files
userAgentPrefix string // Prefix to add to the user agent string
requireSigned bool // Require any pulled image to be signed
}
// requireSubcommand returns an error if no sub command is provided
@@ -70,8 +69,10 @@ func createApp() (*cobra.Command, *globalOptions) {
// (skopeo --tls-verify inspect) (causes a warning) and (skopeo inspect --tls-verify) (no warning).
TraverseChildren: true,
}
if gitCommit != "" {
rootCommand.Version = fmt.Sprintf("%s commit: %s", version.Version, gitCommit)
// We dont use debug.ReadBuildInfo to automate version.Version, because that would not work well for builds from
// a released tarball (e.g. RPM builds).
if commit := gitCommit(); commit != "" {
rootCommand.Version = fmt.Sprintf("%s commit: %s", version.Version, commit)
} else {
rootCommand.Version = version.Version
}
@@ -81,6 +82,7 @@ func createApp() (*cobra.Command, *globalOptions) {
rootCommand.PersistentFlags().BoolVar(&opts.debug, "debug", false, "enable debug output")
rootCommand.PersistentFlags().StringVar(&opts.policyPath, "policy", "", "Path to a trust policy file")
rootCommand.PersistentFlags().BoolVar(&opts.insecurePolicy, "insecure-policy", false, "run the tool without any policy check")
rootCommand.PersistentFlags().BoolVar(&opts.requireSigned, "require-signed", false, "require any pulled image to be signed")
rootCommand.PersistentFlags().StringVar(&opts.registriesDirPath, "registries.d", "", "use registry configuration files in `DIR` (e.g. for container signature storage)")
rootCommand.PersistentFlags().StringVar(&opts.overrideArch, "override-arch", "", "use `ARCH` instead of the architecture of the machine for choosing images")
rootCommand.PersistentFlags().StringVar(&opts.overrideOS, "override-os", "", "use `OS` instead of the running OS for choosing images")
@@ -91,6 +93,7 @@ func createApp() (*cobra.Command, *globalOptions) {
logrus.Fatal("unable to mark registries-conf flag as hidden")
}
rootCommand.PersistentFlags().StringVar(&opts.tmpDir, "tmpdir", "", "directory used to store temporary files")
rootCommand.PersistentFlags().StringVar(&opts.userAgentPrefix, "user-agent-prefix", "", "prefix to add to the user agent string")
flag := commonFlag.OptionalBoolFlag(rootCommand.Flags(), &opts.tlsVerify, "tls-verify", "Require HTTPS and verify certificates when accessing the registry")
flag.Hidden = true
rootCommand.AddCommand(
@@ -112,6 +115,20 @@ func createApp() (*cobra.Command, *globalOptions) {
return rootCommand, &opts
}
// gitCommit returns the git commit for this codebase, if we are built from a git repo; "" otherwise.
func gitCommit() string {
bi, ok := debug.ReadBuildInfo()
if !ok {
logrus.Fatal("runtime.ReadBuildInfo failed")
}
for _, e := range bi.Settings {
if e.Key == "vcs.revision" {
return e.Value
}
}
return ""
}
// before is run by the cli package for any command, before running the command-specific handler.
func (opts *globalOptions) before(cmd *cobra.Command, args []string) error {
if opts.debug {
@@ -120,6 +137,9 @@ func (opts *globalOptions) before(cmd *cobra.Command, args []string) error {
if opts.tlsVerify.Present() {
logrus.Warn("'--tls-verify' is deprecated, please set this on the specific subcommand")
}
if opts.insecurePolicy && opts.requireSigned {
return fmt.Errorf("--insecure-policy and --require-signed are mutually exclusive")
}
return nil
}
@@ -151,7 +171,14 @@ func (opts *globalOptions) getPolicyContext() (*signature.PolicyContext, error)
if err != nil {
return nil, err
}
return signature.NewPolicyContext(policy)
pc, err := signature.NewPolicyContext(policy)
if err != nil {
return nil, err
}
if opts.requireSigned {
pc.RequireSignatureVerification(true)
}
return pc, nil
}
// commandTimeoutContext returns a context.Context and a cancellation callback based on opts.
@@ -168,6 +195,10 @@ func (opts *globalOptions) commandTimeoutContext() (context.Context, context.Can
// newSystemContext returns a *types.SystemContext corresponding to opts.
// It is guaranteed to return a fresh instance, so it is safe to make additional updates to it.
func (opts *globalOptions) newSystemContext() *types.SystemContext {
userAgent := defaultUserAgent
if opts.userAgentPrefix != "" {
userAgent = opts.userAgentPrefix + " " + defaultUserAgent
}
ctx := &types.SystemContext{
RegistriesDirPath: opts.registriesDirPath,
ArchitectureChoice: opts.overrideArch,
@@ -175,7 +206,7 @@ func (opts *globalOptions) newSystemContext() *types.SystemContext {
VariantChoice: opts.overrideVariant,
SystemRegistriesConfPath: opts.registriesConfPath,
BigFilesTemporaryDir: opts.tmpDir,
DockerRegistryUserAgent: defaultUserAgent,
DockerRegistryUserAgent: userAgent,
}
// DEPRECATED: We support this for backward compatibility, but override it if a per-image flag is provided.
if opts.tlsVerify.Present() {

View File

@@ -4,8 +4,8 @@ import (
"bytes"
"testing"
"github.com/containers/image/v5/types"
"github.com/stretchr/testify/assert"
"go.podman.io/image/v5/types"
)
// runSkopeo creates an app object and runs it with args, with an implied first "skopeo".

View File

@@ -6,12 +6,11 @@ import (
"io"
"os"
"github.com/containers/image/v5/manifest"
"github.com/spf13/cobra"
"go.podman.io/image/v5/manifest"
)
type manifestDigestOptions struct {
}
type manifestDigestOptions struct{}
func manifestDigestCmd() *cobra.Command {
var opts manifestDigestOptions

View File

@@ -20,17 +20,17 @@ import (
"sync"
"syscall"
"github.com/containers/common/pkg/retry"
"github.com/containers/image/v5/image"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/pkg/blobinfocache"
"github.com/containers/image/v5/transports"
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types"
"github.com/opencontainers/go-digest"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"go.podman.io/common/pkg/retry"
"go.podman.io/image/v5/image"
"go.podman.io/image/v5/manifest"
"go.podman.io/image/v5/pkg/blobinfocache"
"go.podman.io/image/v5/transports"
"go.podman.io/image/v5/transports/alltransports"
"go.podman.io/image/v5/types"
)
// protocolVersion is semantic version of the protocol used by this proxy.

View File

@@ -8,9 +8,9 @@ import (
"os"
"strings"
"github.com/containers/image/v5/pkg/cli"
"github.com/containers/image/v5/signature"
"github.com/spf13/cobra"
"go.podman.io/image/v5/pkg/cli"
"go.podman.io/image/v5/signature"
)
type standaloneSignOptions struct {
@@ -61,7 +61,7 @@ func (opts *standaloneSignOptions) run(args []string, stdout io.Writer) error {
return fmt.Errorf("Error creating signature: %w", err)
}
if err := os.WriteFile(opts.output, signature, 0644); err != nil {
if err := os.WriteFile(opts.output, signature, 0o644); err != nil {
return fmt.Errorf("Error writing signature to %s: %w", opts.output, err)
}
return nil
@@ -118,7 +118,6 @@ func (opts *standaloneVerifyOptions) run(args []string, stdout io.Writer) error
mech, publicKeyfingerprints, err = signature.NewEphemeralGPGSigningMechanism(publicKeys)
if err != nil {
return fmt.Errorf("Error initializing GPG: %w", err)
}
} else {
mech, err = signature.NewGPGSigningMechanism()
@@ -147,8 +146,7 @@ func (opts *standaloneVerifyOptions) run(args []string, stdout io.Writer) error
// (including things like “✅ Verified by $authority”)
//
// The subcommand is undocumented, and it may be renamed or entirely disappear in the future.
type untrustedSignatureDumpOptions struct {
}
type untrustedSignatureDumpOptions struct{}
func untrustedSignatureDumpCmd() *cobra.Command {
opts := untrustedSignatureDumpOptions{}

View File

@@ -6,10 +6,10 @@ import (
"testing"
"time"
"github.com/containers/image/v5/signature"
"github.com/opencontainers/go-digest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.podman.io/image/v5/signature"
)
const (
@@ -29,7 +29,8 @@ func assertTestFailed(t *testing.T, stdout string, err error, substring string)
}
func TestStandaloneSign(t *testing.T) {
mech, _, err := signature.NewEphemeralGPGSigningMechanism([]byte{})
t.Setenv("GNUPGHOME", "fixtures")
mech, err := signature.NewGPGSigningMechanism()
require.NoError(t, err)
defer mech.Close()
if err := mech.SupportsSigning(); err != nil {
@@ -38,7 +39,6 @@ func TestStandaloneSign(t *testing.T) {
manifestPath := "fixtures/image.manifest.json"
dockerReference := "testing/manifest"
t.Setenv("GNUPGHOME", "fixtures")
// Invalid command-line arguments
for _, args := range [][]string{
@@ -87,9 +87,6 @@ func TestStandaloneSign(t *testing.T) {
require.NoError(t, err)
manifest, err := os.ReadFile(manifestPath)
require.NoError(t, err)
mech, err = signature.NewGPGSigningMechanism()
require.NoError(t, err)
defer mech.Close()
verified, err := signature.VerifyDockerManifestSignature(sig, manifest, dockerReference, mech, fixturesTestKeyFingerprint)
require.NoError(t, err)
assert.Equal(t, dockerReference, verified.DockerReference)

View File

@@ -14,17 +14,17 @@ import (
"strings"
"github.com/Masterminds/semver/v3"
"github.com/containers/common/pkg/retry"
"github.com/containers/image/v5/copy"
"github.com/containers/image/v5/directory"
"github.com/containers/image/v5/docker"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/transports"
"github.com/containers/image/v5/types"
"github.com/opencontainers/go-digest"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"go.podman.io/common/pkg/retry"
"go.podman.io/image/v5/copy"
"go.podman.io/image/v5/directory"
"go.podman.io/image/v5/docker"
"go.podman.io/image/v5/docker/reference"
"go.podman.io/image/v5/manifest"
"go.podman.io/image/v5/transports"
"go.podman.io/image/v5/types"
"gopkg.in/yaml.v3"
)
@@ -182,7 +182,7 @@ func destinationReference(destination string, transport string) (types.ImageRefe
return nil, fmt.Errorf("Destination directory could not be used: %w", err)
}
// the directory holding the image must be created here
if err = os.MkdirAll(destination, 0755); err != nil {
if err = os.MkdirAll(destination, 0o755); err != nil {
return nil, fmt.Errorf("Error creating directory for image %s: %w", destination, err)
}
imageTransport = directory.Transport
@@ -270,7 +270,6 @@ func imagesToCopyFromDir(dirPath string) ([]types.ImageReference, error) {
}
return nil
})
if err != nil {
return sourceReferences,
fmt.Errorf("Error walking the path %q: %w", dirPath, err)
@@ -289,8 +288,11 @@ func imagesToCopyFromRegistry(registryName string, cfg registrySyncConfig, sourc
// override ctx with per-registryName options
serverCtx.DockerCertPath = cfg.CertDir
serverCtx.DockerDaemonCertPath = cfg.CertDir
serverCtx.DockerDaemonInsecureSkipTLSVerify = (cfg.TLSVerify.skip == types.OptionalBoolTrue)
serverCtx.DockerInsecureSkipTLSVerify = cfg.TLSVerify.skip
// Only override TLS verification if explicitly specified in YAML; otherwise, keep CLI/global settings.
if cfg.TLSVerify.skip != types.OptionalBoolUndefined {
serverCtx.DockerDaemonInsecureSkipTLSVerify = (cfg.TLSVerify.skip == types.OptionalBoolTrue)
serverCtx.DockerInsecureSkipTLSVerify = cfg.TLSVerify.skip
}
if cfg.Credentials != (types.DockerAuthConfig{}) {
serverCtx.DockerAuthConfig = &cfg.Credentials
}
@@ -364,7 +366,8 @@ func imagesToCopyFromRegistry(registryName string, cfg registrySyncConfig, sourc
}
repoDescList = append(repoDescList, repoDescriptor{
ImageRefs: sourceReferences,
Context: serverCtx})
Context: serverCtx,
})
}
// include repository descriptors for cfg.ImagesByTagRegex
@@ -664,7 +667,7 @@ func (opts *syncOptions) run(args []string, stdout io.Writer) (retErr error) {
var digestFile *os.File
if opts.digestFile != "" && !opts.dryRun {
digestFile, err = os.OpenFile(opts.digestFile, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
digestFile, err = os.OpenFile(opts.digestFile, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0o644)
if err != nil {
return fmt.Errorf("Error creating digest file: %w", err)
}

View File

@@ -1,11 +1,12 @@
package main
import (
"fmt"
"testing"
"github.com/containers/image/v5/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.podman.io/image/v5/types"
"gopkg.in/yaml.v3"
)
@@ -59,3 +60,42 @@ func TestSync(t *testing.T) {
// FIXME: Much more test coverage
// Actual feature tests exist in integration and systemtest
}
// TestSyncTLSPrecedence validates the interactions of tls-verify in YAML and --src-tls-verify in the CLI.
func TestSyncTLSPrecedence(t *testing.T) {
for _, tt := range []struct {
cli string
yaml string
wantSkip types.OptionalBool
wantDaemonSkip bool
}{
{"--src-tls-verify=false", `# nothing`, types.OptionalBoolTrue, true},
{"--src-tls-verify=true", `# nothing`, types.OptionalBoolFalse, false},
{"", `# nothing`, types.OptionalBoolUndefined, false},
{"--src-tls-verify=false", "tls-verify: true", types.OptionalBoolFalse, false},
{"--src-tls-verify=true", "tls-verify: false", types.OptionalBoolTrue, true},
} {
t.Run(fmt.Sprintf("%#v + %q", tt.cli, tt.yaml), func(t *testing.T) {
opts := fakeImageOptions(t, "src-", true, []string{}, []string{tt.cli})
sourceCtx, err := opts.newSystemContext()
require.NoError(t, err)
var cfg registrySyncConfig
err = yaml.Unmarshal(fmt.Appendf(nil, `
%s
images:
repo: # Specifying an explicit repo+tag avoids imagesToCopyFromRegistry trying to contact the registry.
- latest
`, tt.yaml,
), &cfg)
require.NoError(t, err)
descs, err := imagesToCopyFromRegistry("example.com", cfg, *sourceCtx)
require.NoError(t, err)
require.NotEmpty(t, descs)
ctx := descs[0].Context
require.NotNil(t, ctx)
assert.Equal(t, tt.wantSkip, ctx.DockerInsecureSkipTLSVerify)
assert.Equal(t, tt.wantDaemonSkip, ctx.DockerDaemonInsecureSkipTLSVerify)
})
}
}

View File

@@ -4,9 +4,9 @@ import (
"fmt"
"slices"
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/storage/pkg/unshare"
"github.com/moby/sys/capability"
"go.podman.io/image/v5/transports/alltransports"
"go.podman.io/storage/pkg/unshare"
)
var neededCapabilities = []capability.Cap{

View File

@@ -9,26 +9,27 @@ import (
"strings"
"time"
commonFlag "github.com/containers/common/pkg/flag"
"github.com/containers/common/pkg/retry"
"github.com/containers/image/v5/copy"
"github.com/containers/image/v5/directory"
"github.com/containers/image/v5/manifest"
ociarchive "github.com/containers/image/v5/oci/archive"
ocilayout "github.com/containers/image/v5/oci/layout"
"github.com/containers/image/v5/pkg/cli"
"github.com/containers/image/v5/pkg/cli/sigstore"
"github.com/containers/image/v5/pkg/compression"
"github.com/containers/image/v5/signature/signer"
"github.com/containers/image/v5/storage"
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types"
dockerdistributionerrcode "github.com/docker/distribution/registry/api/errcode"
dockerdistributionapi "github.com/docker/distribution/registry/api/v2"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
commonFlag "go.podman.io/common/pkg/flag"
"go.podman.io/common/pkg/retry"
"go.podman.io/image/v5/copy"
"go.podman.io/image/v5/directory"
"go.podman.io/image/v5/manifest"
ociarchive "go.podman.io/image/v5/oci/archive"
ocilayout "go.podman.io/image/v5/oci/layout"
"go.podman.io/image/v5/pkg/cli"
"go.podman.io/image/v5/pkg/cli/sigstore"
"go.podman.io/image/v5/pkg/compression"
"go.podman.io/image/v5/signature/signer"
"go.podman.io/image/v5/signature/simplesequoia"
"go.podman.io/image/v5/storage"
"go.podman.io/image/v5/transports/alltransports"
"go.podman.io/image/v5/types"
"golang.org/x/term"
)
@@ -266,6 +267,7 @@ type imageDestOptions struct {
compressionFormat string // Format to use for the compression
compressionLevel commonFlag.OptionalInt // Level to use for the compression
precomputeDigests bool // Precompute digests to dedup layers when saving to the docker: transport
forceCompressionFormat bool // Ensures that the compression algorithm set in compressionFormat is used exclusively
imageDestFlagPrefix string
}
@@ -281,6 +283,7 @@ func imageDestFlags(global *globalOptions, shared *sharedImageOptions, deprecate
fs.StringVar(&opts.compressionFormat, flagPrefix+"compress-format", "", "`FORMAT` to use for the compression")
fs.Var(commonFlag.NewOptionalIntValue(&opts.compressionLevel), flagPrefix+"compress-level", "`LEVEL` to use for the compression")
fs.BoolVar(&opts.precomputeDigests, flagPrefix+"precompute-digests", false, "Precompute digests to prevent uploading layers already on the registry using the 'docker' transport.")
fs.BoolVar(&opts.forceCompressionFormat, flagPrefix+"force-compress-format", false, "Force exclusive use of the compression algorithm set in --dest-compress-format")
return fs, &opts
}
@@ -327,6 +330,7 @@ func (opts *imageDestOptions) warnAboutIneffectiveOptions(destTransport types.Im
type sharedCopyOptions struct {
removeSignatures bool // Do not copy signatures from the source image
signByFingerprint string // Sign the image using a GPG key with the specified fingerprint
signBySequoiaFingerprint string // Sign the image using a Sequoia-PGP key with the specified fingerprint
signBySigstoreParamFile string // Sign the image using a sigstore signature per configuration in a param file
signBySigstorePrivateKey string // Sign the image using a sigstore private key
signPassphraseFile string // Path pointing to a passphrase file when signing
@@ -340,6 +344,7 @@ func sharedCopyFlags() (pflag.FlagSet, *sharedCopyOptions) {
fs := pflag.FlagSet{}
fs.BoolVar(&opts.removeSignatures, "remove-signatures", false, "Do not copy signatures from source")
fs.StringVar(&opts.signByFingerprint, "sign-by", "", "Sign the image using a GPG key with the specified `FINGERPRINT`")
fs.StringVar(&opts.signBySequoiaFingerprint, "sign-by-sq-fingerprint", "", "Sign the image using a Sequoia-PGP key with the specified `FINGERPRINT`")
fs.StringVar(&opts.signBySigstoreParamFile, "sign-by-sigstore", "", "Sign the image using a sigstore parameter file at `PATH`")
fs.StringVar(&opts.signBySigstorePrivateKey, "sign-by-sigstore-private-key", "", "Sign the image using a sigstore private key at `PATH`")
fs.StringVar(&opts.signPassphraseFile, "sign-passphrase-file", "", "Read a passphrase for signing an image from `PATH`")
@@ -363,25 +368,43 @@ func (opts *sharedCopyOptions) copyOptions(stdout io.Writer) (*copy.Options, fun
// c/image/copy.Image does allow creating both simple signing and sigstore signatures simultaneously,
// with independent passphrases, but that would make the CLI probably too confusing.
// For now, use the passphrase with either, but only one of them.
if opts.signPassphraseFile != "" && opts.signByFingerprint != "" && opts.signBySigstorePrivateKey != "" {
return nil, nil, fmt.Errorf("Only one of --sign-by and sign-by-sigstore-private-key can be used with sign-passphrase-file")
if opts.signPassphraseFile != "" {
count := 0
if opts.signByFingerprint != "" {
count++
}
if opts.signBySequoiaFingerprint != "" {
count++
}
if opts.signBySigstorePrivateKey != "" {
count++
}
if count > 1 {
return nil, nil, fmt.Errorf("Only one of --sign-by, --sign-by-sq-fingerprint and --sign-by-sigstore-private-key can be used with --sign-passphrase-file")
}
}
// Simple signing does not really allow empty but present passphrases — but for sigstore, cosign does support creating keys encrypted with an empty passphrase;
// so, at least for that case, we must track the distinction between an empty and a missing passphrase precisely.
var passphrase string
passphraseSet := false
if opts.signPassphraseFile != "" {
p, err := cli.ReadPassphraseFile(opts.signPassphraseFile)
if err != nil {
return nil, nil, err
}
passphrase = p
passphraseSet = true
} else if opts.signBySigstorePrivateKey != "" {
p, err := promptForPassphrase(opts.signBySigstorePrivateKey, os.Stdin, os.Stdout)
if err != nil {
return nil, nil, err
}
passphrase = p
passphraseSet = true
} // opts.signByFingerprint triggers a GPG-agent passphrase prompt, possibly using a more secure channel, so we usually shouldnt prompt ourselves if no passphrase was explicitly provided.
// With opts.signBySequoiaFingerprint, we dont prompt for a passphrase (for now??): We dont know whether the key requires a passphrase.
var passphraseBytes []byte
if passphrase != "" {
if passphraseSet {
passphraseBytes = []byte(passphrase)
}
@@ -410,6 +433,19 @@ func (opts *sharedCopyOptions) copyOptions(stdout io.Writer) (*copy.Options, fun
}
signers = append(signers, signer)
}
if opts.signBySequoiaFingerprint != "" {
sqOpts := []simplesequoia.Option{
simplesequoia.WithKeyFingerprint(opts.signBySequoiaFingerprint),
}
if passphraseSet {
sqOpts = append(sqOpts, simplesequoia.WithPassphrase(passphrase))
}
signer, err := simplesequoia.NewSigner(sqOpts...)
if err != nil {
return nil, nil, fmt.Errorf("Error using --sign-by-sq-fingerprint: %w", err)
}
signers = append(signers, signer)
}
succeeded = true
return &copy.Options{

View File

@@ -0,0 +1,5 @@
//go:build !containers_image_sequoia
package main
const buildWithSequoia = false

View File

@@ -0,0 +1,5 @@
//go:build containers_image_sequoia
package main
const buildWithSequoia = true

View File

@@ -6,14 +6,14 @@ import (
"os"
"testing"
"github.com/containers/image/v5/copy"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/types"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.podman.io/image/v5/copy"
"go.podman.io/image/v5/manifest"
"go.podman.io/image/v5/types"
)
func TestNoteCloseFailure(t *testing.T) {
@@ -49,7 +49,8 @@ func fakeGlobalOptions(t *testing.T, flags []string) (*globalOptions, *cobra.Com
// fakeImageOptions creates imageOptions and sets it according to globalFlags/cmdFlags.
func fakeImageOptions(t *testing.T, flagPrefix string, useDeprecatedTLSVerify bool,
globalFlags []string, cmdFlags []string) *imageOptions {
globalFlags []string, cmdFlags []string,
) *imageOptions {
globalOpts, cmd := fakeGlobalOptions(t, globalFlags)
sharedFlags, sharedOpts := sharedImageFlags()
var deprecatedTLSVerifyFlag pflag.FlagSet
@@ -124,7 +125,8 @@ func TestImageOptionsNewSystemContext(t *testing.T) {
// fakeImageDestOptions creates imageDestOptions and sets it according to globalFlags/cmdFlags.
func fakeImageDestOptions(t *testing.T, flagPrefix string, useDeprecatedTLSVerify bool,
globalFlags []string, cmdFlags []string) *imageDestOptions {
globalFlags []string, cmdFlags []string,
) *imageDestOptions {
globalOpts, cmd := fakeGlobalOptions(t, globalFlags)
sharedFlags, sharedOpts := sharedImageFlags()
var deprecatedTLSVerifyFlag pflag.FlagSet
@@ -366,77 +368,121 @@ func fakeSharedCopyOptions(t *testing.T, cmdFlags []string) *sharedCopyOptions {
func TestSharedCopyOptionsCopyOptions(t *testing.T) {
someStdout := bytes.Buffer{}
// Default state
opts := fakeSharedCopyOptions(t, []string{})
res, cleanup, err := opts.copyOptions(&someStdout)
require.NoError(t, err)
defer cleanup()
assert.Equal(t, &copy.Options{
ReportWriter: &someStdout,
}, res)
// Set most flags to non-default values
// This should also test --sign-by-sigstore and --sign-by-sigstore-private-key; we would have
// to create test keys for that.
opts = fakeSharedCopyOptions(t, []string{
"--remove-signatures",
"--sign-by", "gpgFingerprint",
"--format", "oci",
"--preserve-digests",
})
res, cleanup, err = opts.copyOptions(&someStdout)
require.NoError(t, err)
defer cleanup()
assert.Equal(t, &copy.Options{
RemoveSignatures: true,
SignBy: "gpgFingerprint",
ReportWriter: &someStdout,
PreserveDigests: true,
ForceManifestMIMEType: imgspecv1.MediaTypeImageManifest,
}, res)
// --sign-passphrase-file + --sign-by work
passphraseFile, err := os.CreateTemp("", "passphrase") // Eventually we could refer to a passphrase fixture instead
require.NoError(t, err)
defer os.Remove(passphraseFile.Name())
_, err = passphraseFile.WriteString("test-passphrase")
require.NoError(t, err)
opts = fakeSharedCopyOptions(t, []string{
"--sign-by", "gpgFingerprint",
"--sign-passphrase-file", passphraseFile.Name(),
})
res, cleanup, err = opts.copyOptions(&someStdout)
require.NoError(t, err)
defer cleanup()
assert.Equal(t, &copy.Options{
SignBy: "gpgFingerprint",
SignPassphrase: "test-passphrase",
SignSigstorePrivateKeyPassphrase: []byte("test-passphrase"),
ReportWriter: &someStdout,
}, res)
// --sign-passphrase-file + --sign-by-sigstore-private-key should be tested here.
// Invalid --format
opts = fakeSharedCopyOptions(t, []string{"--format", "invalid"})
_, _, err = opts.copyOptions(&someStdout)
assert.Error(t, err)
type tc struct {
options []string
expected copy.Options
expectedSigner bool
}
c := []tc{
{ // Default state
options: []string{},
expected: copy.Options{
ReportWriter: &someStdout,
},
},
// Set most flags to non-default values
// This should also test --sign-by-sigstore and --sign-by-sigstore-private-key; we would have
// to create test keys for that.
// This does not test --sign-by-sq-fingerprint, because that needs to be conditional based on buildWithSequoia.
{
options: []string{
"--remove-signatures",
"--sign-by", "gpgFingerprint",
"--format", "oci",
"--preserve-digests",
},
expected: copy.Options{
RemoveSignatures: true,
SignBy: "gpgFingerprint",
ReportWriter: &someStdout,
PreserveDigests: true,
ForceManifestMIMEType: imgspecv1.MediaTypeImageManifest,
},
},
{ // --sign-passphrase-file + --sign-by work
options: []string{
"--sign-by", "gpgFingerprint",
"--sign-passphrase-file", passphraseFile.Name(),
},
expected: copy.Options{
SignBy: "gpgFingerprint",
SignPassphrase: "test-passphrase",
SignSigstorePrivateKeyPassphrase: []byte("test-passphrase"),
ReportWriter: &someStdout,
},
},
{ // --sign-passphrase-file + --sign-by-sigstore-private-key work
options: []string{
"--sign-by-sigstore-private-key", "/some/key/path.private",
"--sign-passphrase-file", passphraseFile.Name(),
},
expected: copy.Options{
SignPassphrase: "test-passphrase",
SignBySigstorePrivateKeyFile: "/some/key/path.private",
SignSigstorePrivateKeyPassphrase: []byte("test-passphrase"),
ReportWriter: &someStdout,
},
},
{ // --sign-passphrase-file + --sign-by-sigstore-private-key work with an empty passphrase
options: []string{
"--sign-by-sigstore-private-key", "/some/key/path.private",
"--sign-passphrase-file", "./fixtures/empty.passphrase",
},
expected: copy.Options{
SignPassphrase: "",
SignBySigstorePrivateKeyFile: "/some/key/path.private",
SignSigstorePrivateKeyPassphrase: []byte(""),
ReportWriter: &someStdout,
},
},
}
// If Sequoia is supported, --sign-passphrase-file + --sign-by-sq-fingerprint work
if buildWithSequoia {
c = append(c, tc{
options: []string{
"--sign-by-sq-fingerprint", "sqFingerprint",
"--sign-passphrase-file", passphraseFile.Name(),
},
expected: copy.Options{
SignPassphrase: "test-passphrase",
SignSigstorePrivateKeyPassphrase: []byte("test-passphrase"),
ReportWriter: &someStdout,
},
expectedSigner: true,
})
}
for _, c := range c {
opts := fakeSharedCopyOptions(t, c.options)
res, cleanup, err := opts.copyOptions(&someStdout)
require.NoError(t, err)
defer cleanup()
if c.expectedSigner {
assert.NotNil(t, res.Signers)
res.Signers = nil // To allow the comparison below
}
assert.Equal(t, &c.expected, res)
}
// More --sign-passphrase-file, --sign-by-sigstore-private-key, --sign-by-sigstore failure cases should be tested here.
// --sign-passphrase-file not found
opts = fakeSharedCopyOptions(t, []string{
"--sign-by", "gpgFingerprint",
"--sign-passphrase-file", "/dev/null/this/does/not/exist",
})
_, _, err = opts.copyOptions(&someStdout)
assert.Error(t, err)
// --sign-by-sigstore file not found
opts = fakeSharedCopyOptions(t, []string{
"--sign-by-sigstore", "/dev/null/this/does/not/exist",
})
_, _, err = opts.copyOptions(&someStdout)
assert.Error(t, err)
for _, opts := range [][]string{
{"--format", "invalid"}, // Invalid --format
// More --sign-by-sigstore-private-key, --sign-by-sigstore failure cases should be tested here.
// --sign-passphrase-file + more than one key option
{"--sign-by", "gpgFingerprint", "--sign-by-sq-fingerprint", "sqFingerprint", "--sign-passphrase-file", passphraseFile.Name()},
{"--sign-by", "gpgFingerprint", "--sign-by-sigstore-private-key", "sigstorePrivateKey", "--sign-passphrase-file", passphraseFile.Name()},
{"--sign-by-sq-fingerprint", "sqFingerprint", "--sign-by-sigstore-private-key", "sigstorePrivateKey", "--sign-passphrase-file", passphraseFile.Name()},
{"--sign-by", "gpgFingerprint", "--sign-passphrase-file", "/dev/null/this/does/not/exist"}, // --sign-passphrase-file not found
{"--sign-by-sigstore", "/dev/null/this/does/not/exist"}, // --sign-by-sigstore file not found
} {
opts := fakeSharedCopyOptions(t, opts)
_, _, err = opts.copyOptions(&someStdout)
assert.Error(t, err)
}
}
func TestParseManifestFormat(t *testing.T) {
@@ -445,21 +491,31 @@ func TestParseManifestFormat(t *testing.T) {
expectedManifestType string
expectErr bool
}{
{"oci",
{
"oci",
imgspecv1.MediaTypeImageManifest,
false},
{"v2s1",
false,
},
{
"v2s1",
manifest.DockerV2Schema1SignedMediaType,
false},
{"v2s2",
false,
},
{
"v2s2",
manifest.DockerV2Schema2MediaType,
false},
{"",
false,
},
{
"",
true},
{"badValue",
"",
true},
true,
},
{
"badValue",
"",
true,
},
} {
manifestType, err := parseManifestFormat(testCase.formatParam)
if testCase.expectErr {
@@ -480,28 +536,37 @@ func TestImageOptionsAuthfileOverride(t *testing.T) {
expectedAuthfilePath string
}{
// if there is no prefix, only authfile is allowed.
{"",
{
"",
[]string{
"--authfile", "/srv/authfile",
}, "/srv/authfile"},
},
"/srv/authfile",
},
// if authfile and dest-authfile is provided, dest-authfile wins
{"dest-",
{
"dest-",
[]string{
"--authfile", "/srv/authfile",
"--dest-authfile", "/srv/dest-authfile",
}, "/srv/dest-authfile",
},
"/srv/dest-authfile",
},
// if only the shared authfile is provided, authfile must be present in system context
{"dest-",
{
"dest-",
[]string{
"--authfile", "/srv/authfile",
}, "/srv/authfile",
},
"/srv/authfile",
},
// if only the dest authfile is provided, dest-authfile must be present in system context
{"dest-",
{
"dest-",
[]string{
"--dest-authfile", "/srv/dest-authfile",
}, "/srv/dest-authfile",
},
"/srv/dest-authfile",
},
} {
opts := fakeImageOptions(t, testCase.flagPrefix, false, []string{}, testCase.cmdFlags)

View File

@@ -71,8 +71,10 @@ _run_setup() {
# automation, but the sources are in different directories. It's
# possible for a mismatch to happen, but should (hopefully) be unlikely.
# Double-check to make sure.
if ! fgrep -qx "ID=$OS_RELEASE_ID" $mnt/etc/os-release || \
! fgrep -qx "VERSION_ID=$OS_RELEASE_VER" $mnt/etc/os-release; then
# Temporarily, allow running on Rawhide VMs and consuming older binaries:
# that should be compatible enough. Eventually, well stop using Rawhide again.
if ! grep -Fqx "ID=$OS_RELEASE_ID" $mnt/etc/os-release || \
{ ! [[ "$VM_IMAGE_NAME" =~ "rawhide" ]] && ! grep -Fqx "VERSION_ID=$OS_RELEASE_VER" $mnt/etc/os-release; } then
die "Somehow $SKOPEO_CIDEV_CONTAINER_FQIN is not based on $OS_REL_VER."
fi
msg "Copying test binaries from $SKOPEO_CIDEV_CONTAINER_FQIN /usr/local/bin/"
@@ -97,7 +99,7 @@ _run_vendor() {
_run_build() {
make bin/skopeo BUILDTAGS="$BUILDTAGS"
make install PREFIX=/usr/local
make install PREFIX=/usr/local BUILDTAGS="$BUILDTAGS"
}
_run_cross() {

View File

@@ -107,9 +107,14 @@ See containers-sigstore-signing-params.yaml(5) for details about the file format
Add a sigstore signature using a private key at _path_ for an image name corresponding to _destination-image_
**--sign-by-sq-fingerprint** _fingerprint_
Add a “simple signing” signature using a Sequoia-PGP key with the specified _fingerprint_.
**--sign-passphrase-file** _path_
The passphare to use when signing with `--sign-by` or `--sign-by-sigstore-private-key`. Only the first line will be read. A passphrase stored in a file is of questionable security if other users can read this file. Do not use this option if at all avoidable.
The passphrase to use when signing with `--sign-by`, `--sign-by-sigstore-private-key` or `--sign-by-sq-fingerprint`.
Only the first line will be read. A passphrase stored in a file is of questionable security if other users can read this file. Do not use this option if at all avoidable.
**--sign-identity** _reference_
@@ -191,6 +196,10 @@ and will be treated as `zstd` with a warning in that case.
Specifies the compression level to use. The value is specific to the compression algorithm used, e.g. for zstd the accepted values are in the range 1-20 (inclusive), while for gzip it is 1-9 (inclusive).
**--dest-force-compress-format**
Ensures that the compression algorithm set in --dest-compress-format is used exclusively.
**--src-registry-token** _token_
Bearer token for accessing the source registry.
@@ -205,7 +214,7 @@ Precompute digests to ensure layers are not uploaded that already exist on the d
**--retry-times**
The number of times to retry.
The number of times to retry. By default, no retries are attempted.
**--retry-delay**

View File

@@ -70,7 +70,7 @@ Bearer token for accessing the registry.
**--retry-times**
The number of times to retry.
The number of times to retry. By default, no retries are attempted.
**--retry-delay**

View File

@@ -69,7 +69,7 @@ Registry token for accessing the registry.
**--retry-times**
The number of times to retry.
The number of times to retry. By default, no retries are attempted.
**--retry-delay**
@@ -95,6 +95,12 @@ The password to access the registry.
Do not list the available tags from the repository in the output. When `true`, the `RepoTags` array will be empty. Defaults to `false`, which includes all available tags.
**--manifest-digest**=_algorithm_ **EXPERIMENTAL**
Algorithm to use for computing manifest digest (sha256, sha512); defaults to algorithm used in config digest.
**Note:** This flag is experimental and its behavior may change in future releases.
## EXAMPLES
To review information for the image fedora from the docker.io registry:
@@ -186,6 +192,12 @@ $ /bin/skopeo inspect --format '{{ .Env }}' docker://registry.access.redhat.com/
[PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin container=oci]
```
To get the digest using a specific algorithm:
```console
$ skopeo inspect --manifest-digest=sha512 docker://docker.io/library/alpine:latest --format "Digest: {{.Digest}}"
Digest: sha512:5acb33fb56a7791bf0c69d5b19a1c70272148e4107be5261d57305d14e9509792bbca53e5277c456181ecfa1c20ad8427f9b8ba46868020584a819de1128dbd2
```
# SEE ALSO
skopeo(1), skopeo-login(1), docker-login(1), containers-auth.json(5)

View File

@@ -43,7 +43,7 @@ Bearer token for accessing the registry.
**--retry-times**
The number of times to retry.
The number of times to retry. By default, no retries are attempted.
**--retry-delay**

View File

@@ -103,9 +103,14 @@ See containers-sigstore-signing-params.yaml(5) for details about the file format
Add a sigstore signature using a private key at _path_ for an image name corresponding to _destination-image_
**--sign-by-sq-fingerprint** _fingerprint_
Add a “simple signing” signature using a Sequoia-PGP key with the specified _fingerprint_.
**--sign-passphrase-file** _path_
The passphare to use when signing with `--sign-by` or `--sign-by-sigstore-private-key`. Only the first line will be read. A passphrase stored in a file is of questionable security if other users can read this file. Do not use this option if at all avoidable.
The passphrase to use when signing with `--sign-by`, `--sign-by-sigstore-private-key` or `--sign-by-sq-fingerprint`.
Only the first line will be read. A passphrase stored in a file is of questionable security if other users can read this file. Do not use this option if at all avoidable.
**--src-creds** _username[:password]_ for accessing the source registry.
@@ -129,7 +134,7 @@ The passphare to use when signing with `--sign-by` or `--sign-by-sigstore-privat
**--retry-times**
The number of times to retry.
The number of times to retry. By default, no retries are attempted.
**--retry-delay**

View File

@@ -92,10 +92,18 @@ Path to a policy.json file to use for verifying signatures and deciding whether
Use registry configuration files in _dir_ (e.g. for container signature storage), overriding the default path.
**--require-signed**
Require that any pulled image must be signed regardless of what the default or provided trust policy file says.
**--tmpdir** _dir_
Directory used to store temporary files. Defaults to /var/tmp.
**--user-agent-prefix** _prefix_
Prefix to add to the user agent string. The resulting user agent will be in the format "_prefix_ skopeo/_version_".
**--version**, **-v**
Print the version number

118
go.mod
View File

@@ -1,60 +1,59 @@
module github.com/containers/skopeo
// Minimum required golang version
go 1.23.3
go 1.24.6
toolchain go1.24.10
// Warning: Ensure the "go" and "toolchain" versions match exactly to prevent unwanted auto-updates
require (
github.com/Masterminds/semver/v3 v3.4.0
github.com/containers/common v0.64.0
github.com/containers/image/v5 v5.36.0
github.com/containers/ocicrypt v1.2.1
github.com/containers/storage v1.59.0
github.com/docker/distribution v2.8.3+incompatible
github.com/moby/sys/capability v0.4.0
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.2-0.20250724175814-2daaaaf0e7c1
github.com/opencontainers/image-spec v1.1.2-0.20251016170850-26647a49f642
github.com/opencontainers/image-tools v1.0.0-rc3
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.7
github.com/stretchr/testify v1.10.0
golang.org/x/term v0.33.0
github.com/sirupsen/logrus v1.9.4
github.com/spf13/cobra v1.10.2
github.com/spf13/pflag v1.0.10
github.com/stretchr/testify v1.11.1
go.podman.io/common v0.66.2-0.20260202154637-0e2aefda57c9
go.podman.io/image/v5 v5.38.1-0.20260202154637-0e2aefda57c9
go.podman.io/storage v1.61.1-0.20260202154637-0e2aefda57c9
golang.org/x/term v0.39.0
gopkg.in/yaml.v3 v3.0.1
)
require (
cyphar.com/go-pathrs v0.2.1 // indirect
dario.cat/mergo v1.0.2 // indirect
github.com/BurntSushi/toml v1.5.0 // indirect
github.com/BurntSushi/toml v1.6.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.13.0 // indirect
github.com/VividCortex/ewma v1.2.0 // indirect
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
github.com/containerd/cgroups/v3 v3.0.5 // indirect
github.com/clipperhouse/stringish v0.1.1 // indirect
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
github.com/containerd/typeurl/v2 v2.2.3 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.18.2 // indirect
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect
github.com/coreos/go-oidc/v3 v3.14.1 // indirect
github.com/coreos/go-oidc/v3 v3.16.0 // indirect
github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/docker v28.3.3+incompatible // indirect
github.com/docker/docker-credential-helpers v0.9.3 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/docker-credential-helpers v0.9.5 // indirect
github.com/docker/go-connections v0.6.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/go-containerregistry v0.20.3 // indirect
github.com/google/go-containerregistry v0.20.6 // indirect
github.com/google/go-intervals v0.0.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect
@@ -63,54 +62,51 @@ require (
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/compress v1.18.3 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-sqlite3 v1.14.28 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/mattn/go-sqlite3 v1.14.33 // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/mistifyio/go-zfs/v3 v3.0.1 // indirect
github.com/mistifyio/go-zfs/v4 v4.0.0 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/moby/api v1.53.0 // indirect
github.com/moby/moby/client v0.2.2 // indirect
github.com/moby/sys/mountinfo v0.7.2 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/opencontainers/image-spec/schema v0.0.0-20250717171153-ab80ff15c2dd // indirect
github.com/opencontainers/runtime-spec v1.2.1 // indirect
github.com/opencontainers/selinux v1.12.0 // indirect
github.com/opencontainers/runtime-spec v1.3.0 // indirect
github.com/opencontainers/selinux v1.13.1 // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/proglottis/gpgme v0.1.4 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/proglottis/gpgme v0.1.6 // indirect
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.9.0 // indirect
github.com/segmentio/ksuid v1.0.4 // indirect
github.com/sigstore/fulcio v1.6.6 // indirect
github.com/sigstore/protobuf-specs v0.4.1 // indirect
github.com/sigstore/sigstore v1.9.5 // indirect
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
github.com/secure-systems-lab/go-securesystemslib v0.10.0 // indirect
github.com/sigstore/fulcio v1.8.1 // indirect
github.com/sigstore/protobuf-specs v0.5.0 // indirect
github.com/sigstore/sigstore v1.9.6-0.20251111174640-d8ab8afb1326 // indirect
github.com/smallstep/pkcs7 v0.1.1 // indirect
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 // indirect
github.com/sylabs/sif/v2 v2.21.1 // indirect
github.com/sylabs/sif/v2 v2.22.0 // indirect
github.com/tchap/go-patricia/v2 v2.3.3 // indirect
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
github.com/vbatts/tar-split v0.12.1 // indirect
github.com/vbauerster/mpb/v8 v8.10.2 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
go.opentelemetry.io/otel v1.35.0 // indirect
go.opentelemetry.io/otel/metric v1.35.0 // indirect
go.opentelemetry.io/otel/trace v1.35.0 // indirect
golang.org/x/crypto v0.40.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/text v0.27.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
google.golang.org/grpc v1.72.2 // indirect
google.golang.org/protobuf v1.36.6 // indirect
github.com/ulikunitz/xz v0.5.15 // indirect
github.com/vbatts/tar-split v0.12.2 // indirect
github.com/vbauerster/mpb/v8 v8.11.3 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
golang.org/x/crypto v0.47.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.33.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 // indirect
google.golang.org/grpc v1.76.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)

427
go.sum
View File

@@ -1,59 +1,38 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8=
cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc=
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA=
github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok=
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo=
github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8=
github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU=
github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40=
github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk=
github.com/containers/common v0.64.0 h1:Jdjq1e5tqrLov9tcAVc/AfvQCgX4krhcfDBgOXwrSfw=
github.com/containers/common v0.64.0/go.mod h1:bq2UIiFP8vUJdgM+WN8E8jkD7wF69SpDRGzU7epJljg=
github.com/containers/image/v5 v5.36.0 h1:Zh+xFcLjRmicnOT5AFPHH/xj+e3s9ojDN/9X2Kx1+Jo=
github.com/containers/image/v5 v5.36.0/go.mod h1:VZ6cyDHbxZoOt4dklUJ+WNEH9FrgSgfH3qUBYKFlcT0=
github.com/containerd/stargz-snapshotter/estargz v0.18.2 h1:yXkZFYIzz3eoLwlTUZKz2iQ4MrckBxJjkmD16ynUTrw=
github.com/containerd/stargz-snapshotter/estargz v0.18.2/go.mod h1:XyVU5tcJ3PRpkA9XS2T5us6Eg35yM0214Y+wvrZTBrY=
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA=
github.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY=
github.com/containers/ocicrypt v1.2.1 h1:0qIOTT9DoYwcKmxSt8QJt+VzMY18onl9jUXsxpVhSmM=
github.com/containers/ocicrypt v1.2.1/go.mod h1:aD0AAqfMp0MtwqWgHM1bUwe1anx0VazI108CRrSKINQ=
github.com/containers/storage v1.59.0 h1:r2pYSTzQpJTROZbjJQ54Z0GT+rUC6+wHzlSY8yPjsXk=
github.com/containers/storage v1.59.0/go.mod h1:KoAYHnAjP3/cTsRS+mmWZGkufSY2GACiKQ4V3ZLQnR0=
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
github.com/coreos/go-oidc/v3 v3.16.0 h1:qRQUCFstKpXwmEjDQTIbyY/5jF00+asXzSkmkoa/mow=
github.com/coreos/go-oidc/v3 v3.16.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 h1:uX1JmpONuD549D73r6cgnxyUu18Zb7yHAy5AYU0Pm4Q=
github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw=
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -62,33 +41,25 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/docker/cli v28.3.2+incompatible h1:mOt9fcLE7zaACbxW1GeS65RI67wIJrTnqS3hP2huFsY=
github.com/docker/cli v28.3.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v29.1.5+incompatible h1:GckbANUt3j+lsnQ6eCcQd70mNSOismSHWt8vk2AX8ao=
github.com/docker/cli v29.1.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
@@ -98,51 +69,24 @@ github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA=
github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI=
github.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI=
github.com/google/go-containerregistry v0.20.6 h1:cvWX87UxxLgaH76b4hIvya6Dzz9qHB31qAwjAohdSTU=
github.com/google/go-containerregistry v0.20.6/go.mod h1:T0x8MuoAoKX/873bkeSfLD2FAkwCDf9/HZgsFJ02E2Y=
github.com/google/go-intervals v0.0.2 h1:FGrVEiUnTRKR8yE04qzXYaJMtnIYqobR5QbblK3ixcM=
github.com/google/go-intervals v0.0.2/go.mod h1:MkaR3LNRfeKLPmqgJYs4E66z5InYjmCjbbr4TQlcT6Y=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY=
github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
@@ -153,15 +97,11 @@ github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVU
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs=
github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
@@ -169,134 +109,105 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec h1:2tTW6cDth2TSgRbAhD7yjZzTQmcN25sDRPEeinR51yQ=
github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec/go.mod h1:TmwEoGCwIti7BCeJ9hescZgRtatxRE+A72pCoPfmcfk=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0=
github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/mistifyio/go-zfs/v3 v3.0.1 h1:YaoXgBePoMA12+S1u/ddkv+QqxcfiZK4prI6HPnkFiU=
github.com/mistifyio/go-zfs/v3 v3.0.1/go.mod h1:CzVgeB0RvF2EGzQnytKVvVSDwmKJXxkOTUGbNrTja/k=
github.com/mistifyio/go-zfs/v4 v4.0.0 h1:sU0+5dX45tdDK5xNZ3HBi95nxUc48FS92qbIZEvpAg4=
github.com/mistifyio/go-zfs/v4 v4.0.0/go.mod h1:weotFtXTHvBwhr9Mv96KYnDkTPBOHFUbm9cBmQpesL0=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
github.com/moby/moby/api v1.53.0 h1:PihqG1ncw4W+8mZs69jlwGXdaYBeb5brF6BL7mPIS/w=
github.com/moby/moby/api v1.53.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
github.com/moby/moby/client v0.2.2 h1:Pt4hRMCAIlyjL3cr8M5TrXCwKzguebPAc2do2ur7dEM=
github.com/moby/moby/client v0.2.2/go.mod h1:2EkIPVNCqR05CMIzL1mfA07t0HvVUUOl85pasRz/GmQ=
github.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk=
github.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I=
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus=
github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8=
github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y=
github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/onsi/ginkgo/v2 v2.27.5 h1:ZeVgZMx2PDMdJm/+w5fE/OyG6ILo1Y3e+QX4zSR0zTE=
github.com/onsi/ginkgo/v2 v2.27.5/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q=
github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.2-0.20250724175814-2daaaaf0e7c1 h1:4Fo/qGEjjR6Ugeqb0Ldokiy/VYG4669QIlD0+ZN3jQg=
github.com/opencontainers/image-spec v1.1.2-0.20250724175814-2daaaaf0e7c1/go.mod h1:GRy5q9c6/vsqXmQ1I6TL1PkhA64F6eXG9fUOQ9tFvm8=
github.com/opencontainers/image-spec v1.1.2-0.20251016170850-26647a49f642 h1:BNZwTO1e0QJV7HVGz/Qw/tyOE/GnooRmuy6qZnhNGCE=
github.com/opencontainers/image-spec v1.1.2-0.20251016170850-26647a49f642/go.mod h1:GRy5q9c6/vsqXmQ1I6TL1PkhA64F6eXG9fUOQ9tFvm8=
github.com/opencontainers/image-spec/schema v0.0.0-20250717171153-ab80ff15c2dd h1:demTtfPH+DsqagnumQZv8nQrFoUqCJDNVrw+6LsGpm4=
github.com/opencontainers/image-spec/schema v0.0.0-20250717171153-ab80ff15c2dd/go.mod h1:vPOv9cXqxB6ycHY5iVwqL4rkYbwRh46GZj13CfkZ6As=
github.com/opencontainers/image-tools v1.0.0-rc3 h1:ZR837lBIxq6mmwEqfYrbLMuf75eBSHhccVHy6lsBeM4=
github.com/opencontainers/image-tools v1.0.0-rc3/go.mod h1:A9btVpZLzttF4iFaKNychhPyrhfOjJ1OF5KrA8GcLj4=
github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww=
github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8=
github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U=
github.com/opencontainers/runtime-spec v1.3.0 h1:YZupQUdctfhpZy3TM39nN9Ika5CBWT5diQ8ibYCRkxg=
github.com/opencontainers/runtime-spec v1.3.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.13.1 h1:A8nNeceYngH9Ow++M+VVEwJVpdFmrlxsN22F+ISDCJE=
github.com/opencontainers/selinux v1.13.1/go.mod h1:S10WXZ/osk2kWOYKy1x2f/eXF5ZHJoUs8UU/2caNRbg=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/proglottis/gpgme v0.1.4 h1:3nE7YNA70o2aLjcg63tXMOhPD7bplfE5CBdV+hLAm2M=
github.com/proglottis/gpgme v0.1.4/go.mod h1:5LoXMgpE4bttgwwdv9bLs/vwqv3qV7F4glEEZ7mRKrM=
github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=
github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/proglottis/gpgme v0.1.6 h1:8WpQ8VWggLdxkuTnW+sZ1r1t92XBNd8GZNDhQ4Rz+98=
github.com/proglottis/gpgme v0.1.6/go.mod h1:5LoXMgpE4bttgwwdv9bLs/vwqv3qV7F4glEEZ7mRKrM=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/sebdah/goldie/v2 v2.5.5 h1:rx1mwF95RxZ3/83sdS4Yp7t2C5TCokvWP4TBRbAyEWY=
github.com/sebdah/goldie/v2 v2.5.5/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
github.com/secure-systems-lab/go-securesystemslib v0.9.0 h1:rf1HIbL64nUpEIZnjLZ3mcNEL9NBPB0iuVjyxvq3LZc=
github.com/secure-systems-lab/go-securesystemslib v0.9.0/go.mod h1:DVHKMcZ+V4/woA/peqr+L0joiRXbPpQ042GgJckkFgw=
github.com/segmentio/ksuid v1.0.4 h1:sBo2BdShXjmcugAMwjugoGUdUV0pcxY5mW4xKRn3v4c=
github.com/segmentio/ksuid v1.0.4/go.mod h1:/XUiZBD3kVx5SmUOl55voK5yeAbBNNIed+2O73XgrPE=
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
github.com/sigstore/fulcio v1.6.6 h1:XaMYX6TNT+8n7Npe8D94nyZ7/ERjEsNGFC+REdi/wzw=
github.com/sigstore/fulcio v1.6.6/go.mod h1:BhQ22lwaebDgIxVBEYOOqLRcN5+xOV+C9bh/GUXRhOk=
github.com/sigstore/protobuf-specs v0.4.1 h1:5SsMqZbdkcO/DNHudaxuCUEjj6x29tS2Xby1BxGU7Zc=
github.com/sigstore/protobuf-specs v0.4.1/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc=
github.com/sigstore/sigstore v1.9.5 h1:Wm1LT9yF4LhQdEMy5A2JeGRHTrAWGjT3ubE5JUSrGVU=
github.com/sigstore/sigstore v1.9.5/go.mod h1:VtxgvGqCmEZN9X2zhFSOkfXxvKUjpy8RpUW39oCtoII=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/sebdah/goldie/v2 v2.7.1 h1:PkBHymaYdtvEkZV7TmyqKxdmn5/Vcj+8TpATWZjnG5E=
github.com/sebdah/goldie/v2 v2.7.1/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
github.com/secure-systems-lab/go-securesystemslib v0.10.0 h1:l+H5ErcW0PAehBNrBxoGv1jjNpGYdZ9RcheFkB2WI14=
github.com/secure-systems-lab/go-securesystemslib v0.10.0/go.mod h1:MRKONWmRoFzPNQ9USRF9i1mc7MvAVvF1LlW8X5VWDvk=
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/sigstore/fulcio v1.8.1 h1:PmoQv3XmhjR2BWFWw5LcMUXJPmhyizOIL7HeYnpio58=
github.com/sigstore/fulcio v1.8.1/go.mod h1:7tP3KW9eCGlPYRj5N4MSuUOat7CkeIHuXZ2jAUQ+Rwc=
github.com/sigstore/protobuf-specs v0.5.0 h1:F8YTI65xOHw70NrvPwJ5PhAzsvTnuJMGLkA4FIkofAY=
github.com/sigstore/protobuf-specs v0.5.0/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc=
github.com/sigstore/sigstore v1.9.6-0.20251111174640-d8ab8afb1326 h1:s39MsSDVn8LhePV5adidcOjjKHaplLxpHM1mvbC24l4=
github.com/sigstore/sigstore v1.9.6-0.20251111174640-d8ab8afb1326/go.mod h1:xSCb7eki7lCdi+mNh4I4MVpKPP2cWGtDYmSPPmX/K70=
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
github.com/smallstep/pkcs7 v0.1.1 h1:x+rPdt2W088V9Vkjho4KtoggyktZJlMduZAtRHm68LU=
github.com/smallstep/pkcs7 v0.1.1/go.mod h1:dL6j5AIz9GHjVEBTXtW+QliALcgM19RtXaTeyxI+AfA=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6 h1:pnnLyeX7o/5aX8qUQ69P/mLojDqwda8hFOCBTmP/6hw=
github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6/go.mod h1:39R/xuhNgVhi+K0/zst4TLrJrVmbm6LVgl4A0+ZFS5M=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/sylabs/sif/v2 v2.21.1 h1:GZ0b5//AFAqJEChd8wHV/uSKx/l1iuGYwjR8nx+4wPI=
github.com/sylabs/sif/v2 v2.21.1/go.mod h1:YoqEGQnb5x/ItV653bawXHZJOXQaEWpGwHsSD3YePJI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/sylabs/sif/v2 v2.22.0 h1:Y+xXufp4RdgZe02SR3nWEg7S6q4tPWN237WHYzkDSKA=
github.com/sylabs/sif/v2 v2.22.0/go.mod h1:W1XhWTmG1KcG7j5a3KSYdMcUIFvbs240w/MMVW627hs=
github.com/tchap/go-patricia/v2 v2.3.3 h1:xfNEsODumaEcCcY3gI0hYPZ/PcpVv5ju6RMAhgwZDDc=
github.com/tchap/go-patricia/v2 v2.3.3/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0=
github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=
github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
github.com/vbauerster/mpb/v8 v8.10.2 h1:2uBykSHAYHekE11YvJhKxYmLATKHAGorZwFlyNw4hHM=
github.com/vbauerster/mpb/v8 v8.10.2/go.mod h1:+Ja4P92E3/CorSZgfDtK46D7AVbDqmBQRTmyTqPElo0=
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4=
github.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=
github.com/vbauerster/mpb/v8 v8.11.3 h1:iniBmO4ySXCl4gVdmJpgrtormH5uvjpxcx/dMyVU9Jw=
github.com/vbauerster/mpb/v8 v8.11.3/go.mod h1:n9M7WbP0NFjpgKS5XdEC3tMRgZTNM/xtC8zWGkiMuy0=
github.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ=
github.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns=
github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ=
@@ -307,63 +218,45 @@ github.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE=
github.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg=
github.com/ysmood/leakless v0.9.0 h1:qxCG5VirSBvmi3uynXFkcnLMzkphdh3xx5FtrORwDCU=
github.com/ysmood/leakless v0.9.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk=
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs=
go.uber.org/automaxprocs v1.6.0/go.mod h1:ifeIMSnPZuznNm6jmdzmU3/bfk01Fe2fotchwEFJ8r8=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.podman.io/common v0.66.2-0.20260202154637-0e2aefda57c9 h1:iLJYwHeJ548eWlrOgFFnE2QW7A4SHgTHjw+4QJqJ0kY=
go.podman.io/common v0.66.2-0.20260202154637-0e2aefda57c9/go.mod h1:+4bMKfBbfK+qgURkCj6vUtghP5ASjsWyYDI/udLOKxk=
go.podman.io/image/v5 v5.38.1-0.20260202154637-0e2aefda57c9 h1:RFtNtYD33WvYJKAoCzONX2AjP7Ey1MtikfKfJ+dcWCk=
go.podman.io/image/v5 v5.38.1-0.20260202154637-0e2aefda57c9/go.mod h1:imQIBRN6114qH01ttrueVkVCHj28jhsiN7Yubh0CzGc=
go.podman.io/storage v1.61.1-0.20260202154637-0e2aefda57c9 h1:ab5KO2VjxG/VsARN5gBsQoCuQvJr1MYSYf50hpn1ROI=
go.podman.io/storage v1.61.1-0.20260202154637-0e2aefda57c9/go.mod h1:yuLB1ikwsdGrGqSGBWv7fMbOeHupCaMn5iJ1biqxrpI=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI=
golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
@@ -371,41 +264,33 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -415,8 +300,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@@ -426,62 +311,34 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA=
golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950=
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 h1:8XJ4pajGwOlasW+L13MnEGA8W4115jJySQtVfS2/IBU=
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8 h1:M1rk8KBnUsBDg1oPGHNCxG4vc1f49epmTO7xscSajMk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251022142026-3a174f9686a8/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=

View File

@@ -1,8 +0,0 @@
#!/bin/bash
set -e
make PREFIX=/usr install
echo "cd ./integration;" go test "$@" ${BUILDTAGS:+-tags "$BUILDTAGS"}
cd ./integration
go test "$@" ${BUILDTAGS:+-tags "$BUILDTAGS"}

View File

@@ -37,8 +37,4 @@ EOF
export CONTAINERS_STORAGE_CONF=/etc/containers/storage.conf
fi
# Build skopeo, install into /usr/bin
make PREFIX=/usr install "$@"
# Run tests
SKOPEO_BINARY=/usr/bin/skopeo bats --tap systemtest
bats --tap systemtest

View File

@@ -1,27 +0,0 @@
#!/bin/bash
IFS=$'\n'
files=( $(find . -name '*.go' | grep -v '^./vendor/' | sort || true) )
unset IFS
badFiles=()
for f in "${files[@]}"; do
if [ "$(gofmt -s -l < $f)" ]; then
badFiles+=( "$f" )
fi
done
if [ ${#badFiles[@]} -eq 0 ]; then
echo 'Congratulations! All Go source files are properly formatted.'
else
{
echo "These files are not properly gofmt'd:"
for f in "${badFiles[@]}"; do
echo " - $f"
done
echo
echo 'Please reformat the above files using "gofmt -s -w" and commit the result.'
echo
} >&2
exit 1
fi

View File

@@ -1,7 +1,9 @@
package main
const blockedRegistriesConf = "./fixtures/blocked-registries.conf"
const blockedErrorRegex = `.*registry registry-blocked.com is blocked in .*`
const (
blockedRegistriesConf = "./fixtures/blocked-registries.conf"
blockedErrorRegex = `.*registry registry-blocked.com is blocked in .*`
)
func (s *skopeoSuite) TestCopyBlockedSource() {
t := s.T()

View File

@@ -26,8 +26,10 @@ type skopeoSuite struct {
regV2WithAuth *testRegistryV2
}
var _ = suite.SetupAllSuite(&skopeoSuite{})
var _ = suite.TearDownAllSuite(&skopeoSuite{})
var (
_ = suite.SetupAllSuite(&skopeoSuite{})
_ = suite.TearDownAllSuite(&skopeoSuite{})
)
func (s *skopeoSuite) SetupSuite() {
t := s.T()
@@ -50,7 +52,7 @@ func (s *skopeoSuite) TearDownSuite() {
func (s *skopeoSuite) TestVersion() {
t := s.T()
assertSkopeoSucceeds(t, fmt.Sprintf(".*%s version %s.*", skopeoBinary, version.Version),
assertSkopeoSucceeds(t, fmt.Sprintf(".*skopeo version %s.*", version.Version),
"--version")
}

View File

@@ -12,19 +12,21 @@ import (
"net/http"
"net/http/httptest"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/signature"
"github.com/containers/image/v5/types"
digest "github.com/opencontainers/go-digest"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/opencontainers/image-tools/image"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"go.podman.io/image/v5/manifest"
"go.podman.io/image/v5/signature"
"go.podman.io/image/v5/signature/simplesequoia"
"go.podman.io/image/v5/types"
)
const (
@@ -41,14 +43,17 @@ func TestCopy(t *testing.T) {
type copySuite struct {
suite.Suite
cluster *openshiftCluster
registry *testRegistryV2
s1Registry *testRegistryV2
gpgHome string
cluster *openshiftCluster
registry *testRegistryV2
s1Registry *testRegistryV2
gpgHome string
fingerprint string
}
var _ = suite.SetupAllSuite(&copySuite{})
var _ = suite.TearDownAllSuite(&copySuite{})
var (
_ = suite.SetupAllSuite(&copySuite{})
_ = suite.TearDownAllSuite(&copySuite{})
)
func (s *copySuite) SetupSuite() {
t := s.T()
@@ -84,9 +89,15 @@ func (s *copySuite) SetupSuite() {
out := combinedOutputOfCommand(t, gpgBinary, "--armor", "--export", fmt.Sprintf("%s@example.com", key))
err := os.WriteFile(filepath.Join(s.gpgHome, fmt.Sprintf("%s-pubkey.gpg", key)),
[]byte(out), 0600)
[]byte(out), 0o600)
require.NoError(t, err)
}
// Get fingerprint for the personal key (used by some tests)
lines, err := exec.Command(gpgBinary, "--homedir", s.gpgHome, "--with-colons", "--no-permission-warning", "--fingerprint", "personal@example.com").Output()
require.NoError(t, err)
s.fingerprint, err = findFingerprint(lines)
require.NoError(t, err)
}
func (s *copySuite) TearDownSuite() {
@@ -106,7 +117,9 @@ func (s *copySuite) TearDownSuite() {
// and returns a path to a policy, which will be automatically removed when the test completes.
func (s *copySuite) policyFixture(extraSubstitutions map[string]string) string {
t := s.T()
edits := map[string]string{"@keydir@": s.gpgHome}
fixtureDir, err := filepath.Abs("fixtures")
require.NoError(t, err)
edits := map[string]string{"@keydir@": s.gpgHome, "@fixturedir@": fixtureDir}
maps.Copy(edits, extraSubstitutions)
policyPath := fileFromFixture(t, "fixtures/policy.json", edits)
return policyPath
@@ -115,13 +128,13 @@ func (s *copySuite) policyFixture(extraSubstitutions map[string]string) string {
func (s *copySuite) TestCopyWithManifestList() {
t := s.T()
dir := t.TempDir()
assertSkopeoSucceeds(t, "", "copy", knownListImage, "dir:"+dir)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", knownListImage, "dir:"+dir)
}
func (s *copySuite) TestCopyAllWithManifestList() {
t := s.T()
dir := t.TempDir()
assertSkopeoSucceeds(t, "", "copy", "--all", knownListImage, "dir:"+dir)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", "--all", knownListImage, "dir:"+dir)
}
func (s *copySuite) TestCopyAllWithManifestListRoundTrip() {
@@ -130,7 +143,7 @@ func (s *copySuite) TestCopyAllWithManifestListRoundTrip() {
oci2 := t.TempDir()
dir1 := t.TempDir()
dir2 := t.TempDir()
assertSkopeoSucceeds(t, "", "copy", "--multi-arch=all", knownListImage, "oci:"+oci1)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", "--multi-arch=all", knownListImage, "oci:"+oci1)
assertSkopeoSucceeds(t, "", "copy", "--multi-arch=all", "oci:"+oci1, "dir:"+dir1)
assertSkopeoSucceeds(t, "", "copy", "--multi-arch=all", "dir:"+dir1, "oci:"+oci2)
assertSkopeoSucceeds(t, "", "copy", "--multi-arch=all", "oci:"+oci2, "dir:"+dir2)
@@ -145,9 +158,9 @@ func (s *copySuite) TestCopyAllWithManifestListConverge() {
oci2 := t.TempDir()
dir1 := t.TempDir()
dir2 := t.TempDir()
assertSkopeoSucceeds(t, "", "copy", "--multi-arch=all", knownListImage, "oci:"+oci1)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", "--multi-arch=all", knownListImage, "oci:"+oci1)
assertSkopeoSucceeds(t, "", "copy", "--multi-arch=all", "oci:"+oci1, "dir:"+dir1)
assertSkopeoSucceeds(t, "", "copy", "--multi-arch=all", "--format", "oci", knownListImage, "dir:"+dir2)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", "--multi-arch=all", "--format", "oci", knownListImage, "dir:"+dir2)
assertSkopeoSucceeds(t, "", "copy", "--multi-arch=all", "dir:"+dir2, "oci:"+oci2)
assertDirImagesAreEqual(t, dir1, dir2)
out := combinedOutputOfCommand(t, "diff", "-urN", oci1, oci2)
@@ -157,7 +170,7 @@ func (s *copySuite) TestCopyAllWithManifestListConverge() {
func (s *copySuite) TestCopyNoneWithManifestList() {
t := s.T()
dir1 := t.TempDir()
assertSkopeoSucceeds(t, "", "copy", "--multi-arch=index-only", knownListImage, "dir:"+dir1)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", "--multi-arch=index-only", knownListImage, "dir:"+dir1)
manifestPath := filepath.Join(dir1, "manifest.json")
readManifest, err := os.ReadFile(manifestPath)
@@ -174,9 +187,9 @@ func (s *copySuite) TestCopyWithManifestListConverge() {
oci2 := t.TempDir()
dir1 := t.TempDir()
dir2 := t.TempDir()
assertSkopeoSucceeds(t, "", "copy", knownListImage, "oci:"+oci1)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", knownListImage, "oci:"+oci1)
assertSkopeoSucceeds(t, "", "copy", "--multi-arch=all", "oci:"+oci1, "dir:"+dir1)
assertSkopeoSucceeds(t, "", "copy", "--format", "oci", knownListImage, "dir:"+dir2)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", "--format", "oci", knownListImage, "dir:"+dir2)
assertSkopeoSucceeds(t, "", "copy", "--multi-arch=all", "dir:"+dir2, "oci:"+oci2)
assertDirImagesAreEqual(t, dir1, dir2)
out := combinedOutputOfCommand(t, "diff", "-urN", oci1, oci2)
@@ -187,7 +200,8 @@ func (s *copySuite) TestCopyAllWithManifestListStorageFails() {
t := s.T()
storage := t.TempDir()
storage = fmt.Sprintf("[vfs@%s/root+%s/runroot]", storage, storage)
assertSkopeoFails(t, `.*destination transport .* does not support copying multiple images as a group.*`, "copy", "--multi-arch=all", knownListImage, "containers-storage:"+storage+"test")
assertSkopeoFails(t, `.*destination transport .* does not support copying multiple images as a group.*`,
"copy", "--retry-times", "3", "--multi-arch=all", knownListImage, "containers-storage:"+storage+"test")
}
func (s *copySuite) TestCopyWithManifestListStorage() {
@@ -196,8 +210,8 @@ func (s *copySuite) TestCopyWithManifestListStorage() {
storage = fmt.Sprintf("[vfs@%s/root+%s/runroot]", storage, storage)
dir1 := t.TempDir()
dir2 := t.TempDir()
assertSkopeoSucceeds(t, "", "copy", knownListImage, "containers-storage:"+storage+"test")
assertSkopeoSucceeds(t, "", "copy", knownListImage, "dir:"+dir1)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", knownListImage, "containers-storage:"+storage+"test")
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", knownListImage, "dir:"+dir1)
assertSkopeoSucceeds(t, "", "copy", "containers-storage:"+storage+"test", "dir:"+dir2)
decompressDirs(t, dir1, dir2)
assertDirImagesAreEqual(t, dir1, dir2)
@@ -209,9 +223,9 @@ func (s *copySuite) TestCopyWithManifestListStorageMultiple() {
storage = fmt.Sprintf("[vfs@%s/root+%s/runroot]", storage, storage)
dir1 := t.TempDir()
dir2 := t.TempDir()
assertSkopeoSucceeds(t, "", "--override-arch", "amd64", "copy", knownListImage, "containers-storage:"+storage+"test")
assertSkopeoSucceeds(t, "", "--override-arch", "arm64", "copy", knownListImage, "containers-storage:"+storage+"test")
assertSkopeoSucceeds(t, "", "--override-arch", "arm64", "copy", knownListImage, "dir:"+dir1)
assertSkopeoSucceeds(t, "", "--override-arch", "amd64", "copy", "--retry-times", "3", knownListImage, "containers-storage:"+storage+"test")
assertSkopeoSucceeds(t, "", "--override-arch", "arm64", "copy", "--retry-times", "3", knownListImage, "containers-storage:"+storage+"test")
assertSkopeoSucceeds(t, "", "--override-arch", "arm64", "copy", "--retry-times", "3", knownListImage, "dir:"+dir1)
assertSkopeoSucceeds(t, "", "copy", "containers-storage:"+storage+"test", "dir:"+dir2)
decompressDirs(t, dir1, dir2)
assertDirImagesAreEqual(t, dir1, dir2)
@@ -223,12 +237,12 @@ func (s *copySuite) TestCopyWithManifestListDigest() {
dir2 := t.TempDir()
oci1 := t.TempDir()
oci2 := t.TempDir()
m := combinedOutputOfCommand(t, skopeoBinary, "inspect", "--raw", knownListImage)
m := combinedOutputOfCommand(t, skopeoBinary, "inspect", "--retry-times", "3", "--raw", knownListImage)
manifestDigest, err := manifest.Digest([]byte(m))
require.NoError(t, err)
digest := manifestDigest.String()
assertSkopeoSucceeds(t, "", "copy", knownListImageRepo+"@"+digest, "dir:"+dir1)
assertSkopeoSucceeds(t, "", "copy", "--multi-arch=all", knownListImageRepo+"@"+digest, "dir:"+dir2)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", knownListImageRepo+"@"+digest, "dir:"+dir1)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", "--multi-arch=all", knownListImageRepo+"@"+digest, "dir:"+dir2)
assertSkopeoSucceeds(t, "", "copy", "dir:"+dir1, "oci:"+oci1)
assertSkopeoSucceeds(t, "", "copy", "dir:"+dir2, "oci:"+oci2)
out := combinedOutputOfCommand(t, "diff", "-urN", oci1, oci2)
@@ -240,7 +254,7 @@ func (s *copySuite) TestCopyWithDigestfileOutput() {
tempdir := t.TempDir()
dir1 := t.TempDir()
digestOutPath := filepath.Join(tempdir, "digest.txt")
assertSkopeoSucceeds(t, "", "copy", "--digestfile="+digestOutPath, knownListImage, "dir:"+dir1)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", "--digestfile="+digestOutPath, knownListImage, "dir:"+dir1)
readDigest, err := os.ReadFile(digestOutPath)
require.NoError(t, err)
_, err = digest.Parse(string(readDigest))
@@ -253,13 +267,13 @@ func (s *copySuite) TestCopyWithManifestListStorageDigest() {
storage = fmt.Sprintf("[vfs@%s/root+%s/runroot]", storage, storage)
dir1 := t.TempDir()
dir2 := t.TempDir()
m := combinedOutputOfCommand(t, skopeoBinary, "inspect", "--raw", knownListImage)
m := combinedOutputOfCommand(t, skopeoBinary, "inspect", "--retry-times", "3", "--raw", knownListImage)
manifestDigest, err := manifest.Digest([]byte(m))
require.NoError(t, err)
digest := manifestDigest.String()
assertSkopeoSucceeds(t, "", "copy", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
assertSkopeoSucceeds(t, "", "copy", "containers-storage:"+storage+"test@"+digest, "dir:"+dir1)
assertSkopeoSucceeds(t, "", "copy", knownListImageRepo+"@"+digest, "dir:"+dir2)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", knownListImageRepo+"@"+digest, "dir:"+dir2)
decompressDirs(t, dir1, dir2)
assertDirImagesAreEqual(t, dir1, dir2)
}
@@ -270,13 +284,13 @@ func (s *copySuite) TestCopyWithManifestListStorageDigestMultipleArches() {
storage = fmt.Sprintf("[vfs@%s/root+%s/runroot]", storage, storage)
dir1 := t.TempDir()
dir2 := t.TempDir()
m := combinedOutputOfCommand(t, skopeoBinary, "inspect", "--raw", knownListImage)
m := combinedOutputOfCommand(t, skopeoBinary, "inspect", "--retry-times", "3", "--raw", knownListImage)
manifestDigest, err := manifest.Digest([]byte(m))
require.NoError(t, err)
digest := manifestDigest.String()
assertSkopeoSucceeds(t, "", "copy", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
assertSkopeoSucceeds(t, "", "copy", "containers-storage:"+storage+"test@"+digest, "dir:"+dir1)
assertSkopeoSucceeds(t, "", "copy", knownListImageRepo+"@"+digest, "dir:"+dir2)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", knownListImageRepo+"@"+digest, "dir:"+dir2)
decompressDirs(t, dir1, dir2)
assertDirImagesAreEqual(t, dir1, dir2)
}
@@ -285,14 +299,14 @@ func (s *copySuite) TestCopyWithManifestListStorageDigestMultipleArchesBothUseLi
t := s.T()
storage := t.TempDir()
storage = fmt.Sprintf("[vfs@%s/root+%s/runroot]", storage, storage)
m := combinedOutputOfCommand(t, skopeoBinary, "inspect", "--raw", knownListImage)
m := combinedOutputOfCommand(t, skopeoBinary, "inspect", "--retry-times", "3", "--raw", knownListImage)
manifestDigest, err := manifest.Digest([]byte(m))
require.NoError(t, err)
digest := manifestDigest.String()
_, err = manifest.ListFromBlob([]byte(m), manifest.GuessMIMEType([]byte(m)))
require.NoError(t, err)
assertSkopeoSucceeds(t, "", "--override-arch=amd64", "copy", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
assertSkopeoSucceeds(t, "", "--override-arch=arm64", "copy", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
assertSkopeoSucceeds(t, "", "--override-arch=amd64", "copy", "--retry-times", "3", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
assertSkopeoSucceeds(t, "", "--override-arch=arm64", "copy", "--retry-times", "3", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
assertSkopeoFails(t, `.*reading manifest for image instance.*does not exist.*`, "--override-arch=amd64", "inspect", "containers-storage:"+storage+"test@"+digest)
assertSkopeoFails(t, `.*reading manifest for image instance.*does not exist.*`, "--override-arch=amd64", "inspect", "--config", "containers-storage:"+storage+"test@"+digest)
i2 := combinedOutputOfCommand(t, skopeoBinary, "--override-arch=arm64", "inspect", "--config", "containers-storage:"+storage+"test@"+digest)
@@ -306,7 +320,7 @@ func (s *copySuite) TestCopyWithManifestListStorageDigestMultipleArchesFirstUses
t := s.T()
storage := t.TempDir()
storage = fmt.Sprintf("[vfs@%s/root+%s/runroot]", storage, storage)
m := combinedOutputOfCommand(t, skopeoBinary, "inspect", "--raw", knownListImage)
m := combinedOutputOfCommand(t, skopeoBinary, "inspect", "--retry-times", "3", "--raw", knownListImage)
manifestDigest, err := manifest.Digest([]byte(m))
require.NoError(t, err)
digest := manifestDigest.String()
@@ -316,8 +330,8 @@ func (s *copySuite) TestCopyWithManifestListStorageDigestMultipleArchesFirstUses
require.NoError(t, err)
arm64Instance, err := list.ChooseInstance(&types.SystemContext{ArchitectureChoice: "arm64"})
require.NoError(t, err)
assertSkopeoSucceeds(t, "", "--override-arch=amd64", "copy", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
assertSkopeoSucceeds(t, "", "--override-arch=arm64", "copy", knownListImageRepo+"@"+arm64Instance.String(), "containers-storage:"+storage+"test@"+arm64Instance.String())
assertSkopeoSucceeds(t, "", "--override-arch=amd64", "copy", "--retry-times", "3", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
assertSkopeoSucceeds(t, "", "--override-arch=arm64", "copy", "--retry-times", "3", knownListImageRepo+"@"+arm64Instance.String(), "containers-storage:"+storage+"test@"+arm64Instance.String())
i1 := combinedOutputOfCommand(t, skopeoBinary, "--override-arch=amd64", "inspect", "--config", "containers-storage:"+storage+"test@"+digest)
var image1 imgspecv1.Image
err = json.Unmarshal([]byte(i1), &image1)
@@ -341,7 +355,7 @@ func (s *copySuite) TestCopyWithManifestListStorageDigestMultipleArchesSecondUse
t := s.T()
storage := t.TempDir()
storage = fmt.Sprintf("[vfs@%s/root+%s/runroot]", storage, storage)
m := combinedOutputOfCommand(t, skopeoBinary, "inspect", "--raw", knownListImage)
m := combinedOutputOfCommand(t, skopeoBinary, "inspect", "--retry-times", "3", "--raw", knownListImage)
manifestDigest, err := manifest.Digest([]byte(m))
require.NoError(t, err)
digest := manifestDigest.String()
@@ -351,8 +365,8 @@ func (s *copySuite) TestCopyWithManifestListStorageDigestMultipleArchesSecondUse
require.NoError(t, err)
arm64Instance, err := list.ChooseInstance(&types.SystemContext{ArchitectureChoice: "arm64"})
require.NoError(t, err)
assertSkopeoSucceeds(t, "", "--override-arch=amd64", "copy", knownListImageRepo+"@"+amd64Instance.String(), "containers-storage:"+storage+"test@"+amd64Instance.String())
assertSkopeoSucceeds(t, "", "--override-arch=arm64", "copy", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
assertSkopeoSucceeds(t, "", "--override-arch=amd64", "copy", "--retry-times", "3", knownListImageRepo+"@"+amd64Instance.String(), "containers-storage:"+storage+"test@"+amd64Instance.String())
assertSkopeoSucceeds(t, "", "--override-arch=arm64", "copy", "--retry-times", "3", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
i1 := combinedOutputOfCommand(t, skopeoBinary, "--override-arch=amd64", "inspect", "--config", "containers-storage:"+storage+"test@"+amd64Instance.String())
var image1 imgspecv1.Image
err = json.Unmarshal([]byte(i1), &image1)
@@ -376,7 +390,7 @@ func (s *copySuite) TestCopyWithManifestListStorageDigestMultipleArchesThirdUses
t := s.T()
storage := t.TempDir()
storage = fmt.Sprintf("[vfs@%s/root+%s/runroot]", storage, storage)
m := combinedOutputOfCommand(t, skopeoBinary, "inspect", "--raw", knownListImage)
m := combinedOutputOfCommand(t, skopeoBinary, "inspect", "--retry-times", "3", "--raw", knownListImage)
manifestDigest, err := manifest.Digest([]byte(m))
require.NoError(t, err)
digest := manifestDigest.String()
@@ -386,9 +400,9 @@ func (s *copySuite) TestCopyWithManifestListStorageDigestMultipleArchesThirdUses
require.NoError(t, err)
arm64Instance, err := list.ChooseInstance(&types.SystemContext{ArchitectureChoice: "arm64"})
require.NoError(t, err)
assertSkopeoSucceeds(t, "", "--override-arch=amd64", "copy", knownListImageRepo+"@"+amd64Instance.String(), "containers-storage:"+storage+"test@"+amd64Instance.String())
assertSkopeoSucceeds(t, "", "--override-arch=amd64", "copy", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
assertSkopeoSucceeds(t, "", "--override-arch=arm64", "copy", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
assertSkopeoSucceeds(t, "", "--override-arch=amd64", "copy", "--retry-times", "3", knownListImageRepo+"@"+amd64Instance.String(), "containers-storage:"+storage+"test@"+amd64Instance.String())
assertSkopeoSucceeds(t, "", "--override-arch=amd64", "copy", "--retry-times", "3", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
assertSkopeoSucceeds(t, "", "--override-arch=arm64", "copy", "--retry-times", "3", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
assertSkopeoFails(t, `.*reading manifest for image instance.*does not exist.*`, "--override-arch=amd64", "inspect", "--config", "containers-storage:"+storage+"test@"+digest)
i1 := combinedOutputOfCommand(t, skopeoBinary, "--override-arch=amd64", "inspect", "--config", "containers-storage:"+storage+"test@"+amd64Instance.String())
var image1 imgspecv1.Image
@@ -411,7 +425,7 @@ func (s *copySuite) TestCopyWithManifestListStorageDigestMultipleArchesTagAndDig
t := s.T()
storage := t.TempDir()
storage = fmt.Sprintf("[vfs@%s/root+%s/runroot]", storage, storage)
m := combinedOutputOfCommand(t, skopeoBinary, "inspect", "--raw", knownListImage)
m := combinedOutputOfCommand(t, skopeoBinary, "inspect", "--retry-times", "3", "--raw", knownListImage)
manifestDigest, err := manifest.Digest([]byte(m))
require.NoError(t, err)
digest := manifestDigest.String()
@@ -421,8 +435,8 @@ func (s *copySuite) TestCopyWithManifestListStorageDigestMultipleArchesTagAndDig
require.NoError(t, err)
arm64Instance, err := list.ChooseInstance(&types.SystemContext{ArchitectureChoice: "arm64"})
require.NoError(t, err)
assertSkopeoSucceeds(t, "", "--override-arch=amd64", "copy", knownListImage, "containers-storage:"+storage+"test:latest")
assertSkopeoSucceeds(t, "", "--override-arch=arm64", "copy", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
assertSkopeoSucceeds(t, "", "--override-arch=amd64", "copy", "--retry-times", "3", knownListImage, "containers-storage:"+storage+"test:latest")
assertSkopeoSucceeds(t, "", "--override-arch=arm64", "copy", "--retry-times", "3", knownListImageRepo+"@"+digest, "containers-storage:"+storage+"test@"+digest)
assertSkopeoFails(t, `.*reading manifest for image instance.*does not exist.*`, "--override-arch=amd64", "inspect", "--config", "containers-storage:"+storage+"test@"+digest)
i1 := combinedOutputOfCommand(t, skopeoBinary, "--override-arch=arm64", "inspect", "--config", "containers-storage:"+storage+"test:latest")
var image1 imgspecv1.Image
@@ -455,14 +469,14 @@ func (s *copySuite) TestCopyFailsWhenImageOSDoesNotMatchRuntimeOS() {
t := s.T()
storage := t.TempDir()
storage = fmt.Sprintf("[vfs@%s/root+%s/runroot]", storage, storage)
assertSkopeoFails(t, `.*no image found in manifest list for architecture .*, variant .*, OS .*`, "copy", knownWindowsOnlyImage, "containers-storage:"+storage+"test")
assertSkopeoFails(t, `.*no image found in manifest list for architecture .*, variant .*, OS .*`, "copy", "--retry-times", "3", knownWindowsOnlyImage, "containers-storage:"+storage+"test")
}
func (s *copySuite) TestCopySucceedsWhenImageDoesNotMatchRuntimeButWeOverride() {
t := s.T()
storage := t.TempDir()
storage = fmt.Sprintf("[vfs@%s/root+%s/runroot]", storage, storage)
assertSkopeoSucceeds(t, "", "--override-os=windows", "--override-arch=amd64", "copy", knownWindowsOnlyImage, "--retry-times", "3",
assertSkopeoSucceeds(t, "", "--override-os=windows", "--override-arch=amd64", "copy", "--retry-times", "3", knownWindowsOnlyImage,
"containers-storage:"+storage+"test")
}
@@ -491,7 +505,7 @@ func (s *copySuite) TestCopySimple() {
// FIXME: It would be nice to use one of the local Docker registries instead of needing an Internet connection.
// "pull": docker: → dir:
assertSkopeoSucceeds(t, "", "copy", "docker://registry.k8s.io/pause", "dir:"+dir1)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", "docker://registry.k8s.io/pause", "dir:"+dir1)
// "push": dir: → docker(v2s2):
assertSkopeoSucceeds(t, "", "--tls-verify=false", "--debug", "copy", "dir:"+dir1, ourRegistry+"pause:unsigned")
// The result of pushing and pulling is an unmodified image.
@@ -505,7 +519,7 @@ func (s *copySuite) TestCopySimple() {
ociDest := "pause-latest-image"
ociImgName := "pause"
defer os.RemoveAll(ociDest)
assertSkopeoSucceeds(t, "", "copy", "docker://registry.k8s.io/pause:latest", "oci:"+ociDest+":"+ociImgName)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", "docker://registry.k8s.io/pause:latest", "oci:"+ociDest+":"+ociImgName)
_, err := os.Stat(ociDest)
require.NoError(t, err)
// copy exits with status 2 if the image is not found within the container, in some transports.
@@ -514,7 +528,7 @@ func (s *copySuite) TestCopySimple() {
// docker v2s2 -> OCI image layout without image name
ociDest = "pause-latest-noimage"
defer os.RemoveAll(ociDest)
assertSkopeoSucceeds(t, "", "copy", "docker://registry.k8s.io/pause:latest", "oci:"+ociDest)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", "docker://registry.k8s.io/pause:latest", "oci:"+ociDest)
_, err = os.Stat(ociDest)
require.NoError(t, err)
}
@@ -537,9 +551,9 @@ func (s *copySuite) TestCopyEncryption() {
privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
require.NoError(t, err)
err = os.WriteFile(keysDir+"/private.key", privateKeyBytes, 0644)
err = os.WriteFile(keysDir+"/private.key", privateKeyBytes, 0o644)
require.NoError(t, err)
err = os.WriteFile(keysDir+"/public.key", publicKeyBytes, 0644)
err = os.WriteFile(keysDir+"/public.key", publicKeyBytes, 0o644)
require.NoError(t, err)
// We can either perform encryption or decryption on the image.
@@ -564,7 +578,7 @@ func (s *copySuite) TestCopyEncryption() {
invalidPrivateKey, err := rsa.GenerateKey(rand.Reader, 4096)
require.NoError(t, err)
invalidPrivateKeyBytes := x509.MarshalPKCS1PrivateKey(invalidPrivateKey)
err = os.WriteFile(keysDir+"/invalid_private.key", invalidPrivateKeyBytes, 0644)
err = os.WriteFile(keysDir+"/invalid_private.key", invalidPrivateKeyBytes, 0o644)
require.NoError(t, err)
assertSkopeoFails(t, ".*no suitable key unwrapper found or none of the private keys could be used for decryption.*",
"copy", "--decryption-key", keysDir+"/invalid_private.key",
@@ -600,7 +614,6 @@ func (s *copySuite) TestCopyEncryption() {
// After successful decryption we should find the gzipped layers from the nginx image
matchLayerBlobBinaryType(t, partiallyDecryptedImgDir+"/blobs/sha256", "application/x-gzip", 3)
}
func matchLayerBlobBinaryType(t *testing.T, ociImageDirPath string, contentType string, matchCount int) {
@@ -745,7 +758,7 @@ func (s *copySuite) TestCopyOCIRoundTrip() {
// --sign-by and --policy copy, primarily using atomic:
func (s *copySuite) TestCopySignatures() {
t := s.T()
mech, _, err := signature.NewEphemeralGPGSigningMechanism([]byte{})
mech, err := signature.NewGPGSigningMechanism()
require.NoError(t, err)
defer mech.Close()
if err := mech.SupportsSigning(); err != nil { // FIXME? Test that verification and policy enforcement works, using signatures from fixtures
@@ -759,10 +772,10 @@ func (s *copySuite) TestCopySignatures() {
// type: reject
assertSkopeoFails(t, fmt.Sprintf(".*Source image rejected: Running image %s:latest is rejected by policy.*", testFQIN),
"--policy", policy, "copy", testFQIN+":latest", dirDest)
"--policy", policy, "copy", "--retry-times", "3", testFQIN+":latest", dirDest)
// type: insecureAcceptAnything
assertSkopeoSucceeds(t, "", "--policy", policy, "copy", "docker://quay.io/openshift/origin-hello-openshift", dirDest)
assertSkopeoSucceeds(t, "", "--policy", policy, "copy", "--retry-times", "3", "docker://quay.io/openshift/origin-hello-openshift", dirDest)
// type: signedBy
// Sign the images
@@ -776,9 +789,10 @@ func (s *copySuite) TestCopySignatures() {
// Verify that mis-signed images are rejected
assertSkopeoSucceeds(t, "", "--tls-verify=false", "copy", "atomic:localhost:5006/myns/personal:personal", "atomic:localhost:5006/myns/official:attack")
assertSkopeoSucceeds(t, "", "--tls-verify=false", "copy", "atomic:localhost:5006/myns/official:official", "atomic:localhost:5006/myns/personal:attack")
assertSkopeoFails(t, ".*Source image rejected: Invalid GPG signature.*",
// "Invalid GPG signature" is reported by the gpgme mechanism; "Missing key: $fingerprint" or "Missing key $fingerprint" by Sequoia.
assertSkopeoFails(t, ".*Source image rejected: (Invalid GPG signature|Missing key).*",
"--tls-verify=false", "--policy", policy, "copy", "atomic:localhost:5006/myns/personal:attack", dirDest)
assertSkopeoFails(t, ".*Source image rejected: Invalid GPG signature.*",
assertSkopeoFails(t, ".*Source image rejected: (Invalid GPG signature|Missing key).*",
"--tls-verify=false", "--policy", policy, "copy", "atomic:localhost:5006/myns/official:attack", dirDest)
// Verify that signed identity is verified.
@@ -791,7 +805,8 @@ func (s *copySuite) TestCopySignatures() {
// Verify that cosigning requirements are enforced
assertSkopeoSucceeds(t, "", "--tls-verify=false", "copy", "atomic:localhost:5006/myns/official:official", "atomic:localhost:5006/myns/cosigned:cosigned")
assertSkopeoFails(t, ".*Source image rejected: Invalid GPG signature.*",
// "Invalid GPG signature" is reported by the gpgme mechanism; "Missing key: $fingerprint" or "Missing key $fingerprint" by Sequoia.
assertSkopeoFails(t, ".*Source image rejected: (Invalid GPG signature|Missing key).*",
"--tls-verify=false", "--policy", policy, "copy", "atomic:localhost:5006/myns/cosigned:cosigned", dirDest)
assertSkopeoSucceeds(t, "", "--tls-verify=false", "copy", "--sign-by", "personal@example.com", "atomic:localhost:5006/myns/official:official", "atomic:localhost:5006/myns/cosigned:cosigned")
@@ -801,7 +816,7 @@ func (s *copySuite) TestCopySignatures() {
// --policy copy for dir: sources
func (s *copySuite) TestCopyDirSignatures() {
t := s.T()
mech, _, err := signature.NewEphemeralGPGSigningMechanism([]byte{})
mech, err := signature.NewGPGSigningMechanism()
require.NoError(t, err)
defer mech.Close()
if err := mech.SupportsSigning(); err != nil { // FIXME? Test that verification and policy enforcement works, using signatures from fixtures
@@ -812,7 +827,7 @@ func (s *copySuite) TestCopyDirSignatures() {
topDirDest := "dir:" + topDir
for _, suffix := range []string{"/dir1", "/dir2", "/restricted/personal", "/restricted/official", "/restricted/badidentity", "/dest"} {
err := os.MkdirAll(topDir+suffix, 0755)
err := os.MkdirAll(topDir+suffix, 0o755)
require.NoError(t, err)
}
@@ -836,7 +851,8 @@ func (s *copySuite) TestCopyDirSignatures() {
// Verify that correct images are accepted
assertSkopeoSucceeds(t, "", "--policy", policy, "copy", topDirDest+"/restricted/official", topDirDest+"/dest")
// ... and that mis-signed images are rejected.
assertSkopeoFails(t, ".*Source image rejected: Invalid GPG signature.*",
// "Invalid GPG signature" is reported by the gpgme mechanism; "Missing key: $fingerprint" or "Missing key $fingerprint" by Sequoia.
assertSkopeoFails(t, ".*Source image rejected: (Invalid GPG signature|Missing key).*",
"--policy", policy, "copy", topDirDest+"/restricted/personal", topDirDest+"/dest")
// Verify that the signed identity is verified.
@@ -846,6 +862,39 @@ func (s *copySuite) TestCopyDirSignatures() {
"--policy", policy, "copy", topDirDest+"/restricted/badidentity", topDirDest+"/dest")
}
func (s *copySuite) TestCopySequoiaSignatures() {
t := s.T()
signer, err := simplesequoia.NewSigner(simplesequoia.WithSequoiaHome(testSequoiaHome), simplesequoia.WithKeyFingerprint(testSequoiaKeyFingerprint))
if err != nil {
t.Skipf("Sequoia not supported: %v", err)
}
signer.Close()
const ourRegistry = "docker://" + v2DockerRegistryURL + "/"
dirDest := "dir:" + t.TempDir()
policy := s.policyFixture(nil)
registriesDir := t.TempDir()
registriesFile := fileFromFixture(t, "fixtures/registries.yaml",
map[string]string{"@lookaside@": t.TempDir(), "@split-staging@": "/var/empty", "@split-read@": "file://var/empty"})
err = os.Symlink(registriesFile, filepath.Join(registriesDir, "registries.yaml"))
require.NoError(t, err)
// Sign the images
absSequoiaHome, err := filepath.Abs(testSequoiaHome)
require.NoError(t, err)
t.Setenv("SEQUOIA_HOME", absSequoiaHome)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", "--dest-tls-verify=false", "--sign-by-sq-fingerprint", testSequoiaKeyFingerprint,
testFQIN+":1.26", ourRegistry+"sequoia-no-passphrase")
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", "--dest-tls-verify=false", "--sign-by-sq-fingerprint", testSequoiaKeyFingerprintWithPassphrase,
"--sign-passphrase-file", filepath.Join(absSequoiaHome, "with-passphrase.passphrase"),
testFQIN+":1.26.1", ourRegistry+"sequoia-with-passphrase")
// Verify that we can pull them
assertSkopeoSucceeds(t, "", "--policy", policy, "copy", "--src-tls-verify=false", ourRegistry+"sequoia-no-passphrase", dirDest)
assertSkopeoSucceeds(t, "", "--policy", policy, "copy", "--src-tls-verify=false", ourRegistry+"sequoia-with-passphrase", dirDest)
}
// Compression during copy
func (s *copySuite) TestCopyCompression() {
t := s.T()
@@ -860,7 +909,7 @@ func (s *copySuite) TestCopyCompression() {
{"uncompressed-image-s2", "atomic:localhost:5000/myns/compression:s2"},
} {
dir := filepath.Join(topDir, fmt.Sprintf("case%d", i))
err := os.MkdirAll(dir, 0755)
err := os.MkdirAll(dir, 0o755)
require.NoError(t, err)
assertSkopeoSucceeds(t, "", "--tls-verify=false", "copy", "dir:fixtures/"+c.fixture, c.remote)
@@ -902,7 +951,7 @@ func findRegularFiles(t *testing.T, root string) []string {
// --sign-by and policy use for docker: with lookaside
func (s *copySuite) TestCopyDockerLookaside() {
t := s.T()
mech, _, err := signature.NewEphemeralGPGSigningMechanism([]byte{})
mech, err := signature.NewGPGSigningMechanism()
require.NoError(t, err)
defer mech.Close()
if err := mech.SupportsSigning(); err != nil { // FIXME? Test that verification and policy enforcement works, using signatures from fixtures
@@ -913,7 +962,7 @@ func (s *copySuite) TestCopyDockerLookaside() {
tmpDir := t.TempDir()
copyDest := filepath.Join(tmpDir, "dest")
err = os.Mkdir(copyDest, 0755)
err = os.Mkdir(copyDest, 0o755)
require.NoError(t, err)
dirDest := "dir:" + copyDest
plainLookaside := filepath.Join(tmpDir, "lookaside")
@@ -927,7 +976,7 @@ func (s *copySuite) TestCopyDockerLookaside() {
policy := s.policyFixture(nil)
registriesDir := filepath.Join(tmpDir, "registries.d")
err = os.Mkdir(registriesDir, 0755)
err = os.Mkdir(registriesDir, 0o755)
require.NoError(t, err)
registriesFile := fileFromFixture(t, "fixtures/registries.yaml",
map[string]string{"@lookaside@": plainLookaside, "@split-staging@": splitLookasideStaging, "@split-read@": splitLookasideReadServer.URL})
@@ -971,7 +1020,7 @@ func (s *copySuite) TestCopyDockerLookaside() {
// atomic: and docker: X-Registry-Supports-Signatures works and interoperates
func (s *copySuite) TestCopyAtomicExtension() {
t := s.T()
mech, _, err := signature.NewEphemeralGPGSigningMechanism([]byte{})
mech, err := signature.NewGPGSigningMechanism()
require.NoError(t, err)
defer mech.Close()
if err := mech.SupportsSigning(); err != nil { // FIXME? Test that the reading/writing works using signatures from fixtures
@@ -980,7 +1029,7 @@ func (s *copySuite) TestCopyAtomicExtension() {
topDir := t.TempDir()
for _, subdir := range []string{"dirAA", "dirAD", "dirDA", "dirDD", "registries.d"} {
err := os.MkdirAll(filepath.Join(topDir, subdir), 0755)
err := os.MkdirAll(filepath.Join(topDir, subdir), 0o755)
require.NoError(t, err)
}
registriesDir := filepath.Join(topDir, "registries.d")
@@ -1031,7 +1080,7 @@ func (s *copySuite) TestCopyVerifyingMirroredSignatures() {
t := s.T()
const regPrefix = "docker://localhost:5006/myns/mirroring-"
mech, _, err := signature.NewEphemeralGPGSigningMechanism([]byte{})
mech, err := signature.NewGPGSigningMechanism()
require.NoError(t, err)
defer mech.Close()
if err := mech.SupportsSigning(); err != nil { // FIXME? Test that verification and policy enforcement works, using signatures from fixtures
@@ -1166,14 +1215,14 @@ func (s *copySuite) TestCopyPreserveDigests() {
t := s.T()
topDir := t.TempDir()
assertSkopeoSucceeds(t, "", "copy", knownListImage, "--multi-arch=all", "--preserve-digests", "dir:"+topDir)
assertSkopeoFails(t, ".*Instructed to preserve digests.*", "copy", knownListImage, "--multi-arch=all", "--preserve-digests", "--format=oci", "dir:"+topDir)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", knownListImage, "--multi-arch=all", "--preserve-digests", "dir:"+topDir)
assertSkopeoFails(t, ".*Instructed to preserve digests.*", "copy", "--retry-times", "3", knownListImage, "--multi-arch=all", "--preserve-digests", "--format=oci", "dir:"+topDir)
}
func (s *copySuite) testCopySchemaConversionRegistries(t *testing.T, schema1Registry, schema2Registry string) {
topDir := t.TempDir()
for _, subdir := range []string{"input1", "input2", "dest2"} {
err := os.MkdirAll(filepath.Join(topDir, subdir), 0755)
err := os.MkdirAll(filepath.Join(topDir, subdir), 0o755)
require.NoError(t, err)
}
input1Dir := filepath.Join(topDir, "input1")
@@ -1244,3 +1293,87 @@ func (s *copySuite) TestCopyFailsWhenReferenceIsInvalid() {
t := s.T()
assertSkopeoFails(t, `.*Invalid image name.*`, "copy", "unknown:transport", "unknown:test")
}
func (s *copySuite) TestInsecurePolicyAndRequireSignedConflict() {
t := s.T()
assertSkopeoFails(t, ".*--insecure-policy and --require-signed are mutually exclusive.*",
"--insecure-policy", "--require-signed", "inspect", "dir:/nonexistent")
}
func (s *copySuite) TestRequireSignedAcceptsSignedImage() {
t := s.T()
mech, err := signature.NewGPGSigningMechanism()
require.NoError(t, err)
defer mech.Close()
if err := mech.SupportsSigning(); err != nil {
t.Skipf("Signing not supported: %v", err)
}
srcDir := t.TempDir()
// get an image to work with
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3",
testFQIN64, "dir:"+srcDir)
// first, sanity-check that without --require-signed, we can copy it since by default, `dir:` is insecureAcceptAnything
destDir1 := t.TempDir()
assertSkopeoSucceeds(t, "", "copy", "dir:"+srcDir, "dir:"+destDir1)
// now verify that copying fails with --require-signed
destDir2 := t.TempDir()
assertSkopeoFails(t, ".*Source image rejected: No signature verification policy found for image.*",
"--require-signed", "copy",
"dir:"+srcDir, "dir:"+destDir2)
// sign the image
manifestPath := filepath.Join(srcDir, "manifest.json")
signaturePath := filepath.Join(srcDir, "signature-1")
dockerReference := "localhost/test:latest"
assertSkopeoSucceeds(t, "", "standalone-sign",
"-o", signaturePath,
manifestPath, dockerReference, s.fingerprint)
// sanity-check signature file is there
_, err = os.Stat(signaturePath)
require.NoError(t, err)
// create a basic policy that requires signatures
policy := map[string]any{
"default": []map[string]any{{
"type": "signedBy",
"keyType": "GPGKeys",
"keyPath": filepath.Join(s.gpgHome, "personal-pubkey.gpg"),
"signedIdentity": map[string]any{
"type": "exactRepository",
"dockerRepository": dockerReference,
},
}},
}
policyJSON, err := json.Marshal(policy)
require.NoError(t, err)
policyFile, err := os.CreateTemp("", "policy-*.json")
require.NoError(t, err)
t.Cleanup(func() { os.Remove(policyFile.Name()) })
_, err = policyFile.Write(policyJSON)
require.NoError(t, err)
err = policyFile.Close()
require.NoError(t, err)
// now copying with --require-signed should pass
destDir3 := t.TempDir()
assertSkopeoSucceeds(t, "", "--policy", policyFile.Name(), "--require-signed", "copy",
"dir:"+srcDir, "dir:"+destDir3)
// Delete the signature and sanity-check that copying fails. This doesn't
// strictly test --require-signed, but rather the PolicyRequirements logic, but
// it makes the test feel complete.
err = os.Remove(signaturePath)
require.NoError(t, err)
destDir4 := t.TempDir()
assertSkopeoFails(t, ".*Source image rejected: A signature was required, but no signature exists.*",
"--policy", policyFile.Name(), "--require-signed", "copy",
"dir:"+srcDir, "dir:"+destDir4)
}

1
integration/fixtures/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/data/pgp.cert.d/_sequoia*

Binary file not shown.

View File

@@ -0,0 +1,38 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
xjMEaGwFVhYJKwYBBAHaRw8BAQdAZzfnqEAgvE3RoCtPWEOc3Xp8oMURR0qjq+Ru
PHJrc6TCwAsEHxYKAH0FgmhsBVYDCwkHCRD2+Qi2+kiiKUcUAAAAAAAeACBzYWx0
QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmcEjRQtILaFnIhczxeUkcfW0KMHEZ30
wTdJ1v1iHB7NKQMVCggCmwECHgkWIQRQ3eiY305IdVyMK3r2+Qi2+kiiKQAA86gA
/1ZkXWPHUxh3nQu/EL72ZeP9k/SLWkEuNKs6dJrmRud9AQCHbWwSUwKyt12EFVt/
QvMFSQ95brUxsWLHgFMPpNfWAc0aU2tvcGVvIFNlcXVvaWEgdGVzdGluZyBrZXnC
wA4EExYKAIAFgmhsBVYDCwkHCRD2+Qi2+kiiKUcUAAAAAAAeACBzYWx0QG5vdGF0
aW9ucy5zZXF1b2lhLXBncC5vcmctF7xuY06GUyedOGjd2iNKwab85gV64zEAGKgi
ExHRxgMVCggCmQECmwECHgkWIQRQ3eiY305IdVyMK3r2+Qi2+kiiKQAA3SEBAMe1
y6rWaPjDpkeiDthLV1Umr6NsXVBv/IJTcP9RM4quAQCwmlsdQMddCsc+K3Y5KH88
saIG0/MRZaPJdsd8vRGUCs4zBGhsBVYWCSsGAQQB2kcPAQEHQLN8yt/21QDMzcB4
2bzFRg1LpkFZWECjkb2ty7Iju/aOwsC/BBgWCgExBYJobAVWCRD2+Qi2+kiiKUcU
AAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmce9QEurrtI24ys
vXssO/40rI5rlsNokEEFr7CVwVgWvAKbAr6gBBkWCgBvBYJobAVWCRB63Ra9Qdgp
tkcUAAAAAAAeACBzYWx0QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmcBWCJsdUfj
oYpld4qcYBqjxsyScwpID2vkNlYMLmS+IhYhBKyZqvZ6WI3zgaapXHrdFr1B2Cm2
AAAEZwEA/UhpNN1XElYx6Xq+JMKlXywoIgButkQy1+H2EcRBeHsBAM7lq8BXvRKz
bDjRlgxiIAYl77p7ihVQ5NYcuZcAlH0CFiEEUN3omN9OSHVcjCt69vkItvpIoikA
AJcwAP9D4spfb28k16w2cemrWAtAE1WUgV8V+OEpE7+gpV+17gEA+0Kzf7jBHgd3
pBAWwttuRd8OHlZZzKs3f26z28I6mgLOMwRobAVWFgkrBgEEAdpHDwEBB0DPyS14
jQk1mSWNmuYR4P9M5zOfU2mkhwaqx1l3OWTZD8LAvwQYFgoBMQWCaGwFVgkQ9vkI
tvpIoilHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3Jn+wfK
FmPmtrsi0sY5zIq9KFmbrQyhXz/VZIw6K8D1zdECmyC+oAQZFgoAbwWCaGwFVgkQ
bwujLUxU69BHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3Jn
xF3KXB4+dN9suOhCD2XkYlAWUJ4GVBVV2wAmdQAueyEWIQTv1sMw2eUTIMQmb7Zv
C6MtTFTr0AAA/LYA/iBkRh6dGbp76VzuuHVNUNgTqvXgz9FjizZGJKnVZctXAPwL
TlHxcH6XX96AuiCy9QAMUpm8ZvMu8TAgjgOrlFPKCBYhBFDd6JjfTkh1XIwrevb5
CLb6SKIpAAA0rQD9HWbBeSoshjH6/k5ntZjOfIAha4/TLlBrMq2w+t4LWD0A/2q5
DEbYh6PwMidDxXteyHWf4Qnr0vH8vip9d+WHbDYEzjgEaGwFVhIKKwYBBAGXVQEF
AQEHQLxXHw9STOAhb2PLEjrl3uQDwpaXIdigg67vId0jSstVAwEIB8LAAAQYFgoA
cgWCaGwFVgkQ9vkItvpIoilHFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9p
YS1wZ3Aub3Jn8bvuQCv3uEYJtK6h5y5e4AY9lJtVXx3brexR5bmFCwcCmwwWIQRQ
3eiY305IdVyMK3r2+Qi2+kiiKQAAEzkA/Az97rdlp3hf97S6a5AxU8pTry4gKI63
lwKtBAT+uF/pAP9lAziQRlNEa1sX6qCXrQqeA/aQ0nj9gRJ1Wvi1PMxWBA==
=7jmE
-----END PGP PUBLIC KEY BLOCK-----

View File

@@ -13,6 +13,20 @@
"keyPath": "@keydir@/personal-pubkey.gpg"
}
],
"localhost:5555/sequoia-no-passphrase": [
{
"type": "signedBy",
"keyType": "GPGKeys",
"keyPath": "@fixturedir@/no-passphrase.pub"
}
],
"localhost:5555/sequoia-with-passphrase": [
{
"type": "signedBy",
"keyType": "GPGKeys",
"keyPath": "@fixturedir@/with-passphrase.pub"
}
],
"localhost:5000/myns/extension": [
{
"type": "signedBy",

View File

@@ -0,0 +1 @@
WithPassphrase123

View File

@@ -0,0 +1,39 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
xjMEaGwF3RYJKwYBBAHaRw8BAQdAouHF6y7foOScub78AINlTzXnEQrYrAJyH8fr
3biwuMzCwAsEHxYKAH0FgmhsBd0DCwkHCRAtEaGaukHGrkcUAAAAAAAeACBzYWx0
QG5vdGF0aW9ucy5zZXF1b2lhLXBncC5vcmdRHdDbkndmp7Q96YisL7ezwrLxSfQj
46zFb8wob+6yvgMVCggCmwECHgkWIQQfWCUoW3heHbE7820tEaGaukHGrgAAbd8A
/3iwAF7qTVgqqCqLVIj8oJxrZr/jWbHbjO1DzFafQQjMAQDwwOuL9dhy9Q7N5UkW
x3kq3WLEIuogh+0meAwfMrJMAM0qU2tvcGVvIFNlcXVvaWEgdGVzdGluZyBrZXkg
d2l0aCBwYXNzcGhyYXNlwsAOBBMWCgCABYJobAXdAwsJBwkQLRGhmrpBxq5HFAAA
AAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1wZ3Aub3JnwGFwQkw9BWc963pG
lBgz8D9CbfsqoDS58GXyd24W8g4DFQoIApkBApsBAh4JFiEEH1glKFt4Xh2xO/Nt
LRGhmrpBxq4AAKTpAPsHMyzeL+fT/EdPbU/+fi/+RbGuRQH5QHtzaDfAu+ZGUwD+
Oeoi7OOy8+bgvnEdj31TohAGEexTvhMIILglL9ymTgfOMwRobAXdFgkrBgEEAdpH
DwEBB0DNeYLgt7VaYbdJ3TyTqiYp7pEuXYVYjeqRtt055Hs60cLAvwQYFgoBMQWC
aGwF3QkQLRGhmrpBxq5HFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9pYS1w
Z3Aub3Jnr08xd/fCttifQZ/b+oVq2huO6HT9zpTITLIzPLLBI6cCmwK+oAQZFgoA
bwWCaGwF3QkQVNJA3Fgs7h9HFAAAAAAAHgAgc2FsdEBub3RhdGlvbnMuc2VxdW9p
YS1wZ3Aub3JnN1Kokqv7bIxnM7EODP0bX7yuAV8OP+kCivD84d8TrkEWIQQemi42
PEh1us0v16FU0kDcWCzuHwAAUisBAKBMLjhkVO+KCFNKxYoak/Hj7VAHwiqnEAXB
aMstWEE1AP9rVWwZ85IdlSejb475H9HGl+Nl0a5BOioR/Y+Kl15UBxYhBB9YJShb
eF4dsTvzbS0RoZq6QcauAAAKnAEAvgb1r2cteb+9wd9U5vYZ7/xXKEljojjA7CQT
QFmecoYBAO3/rNK3xYcKleni3lknNhzQap+Ed6ri2WVQCKujRgIAzjMEaGwF3RYJ
KwYBBAHaRw8BAQdA1JYMc2I192WwvCI/qFcLrwmFPwDDkHvNDDt4Kc2ziHjCwL8E
GBYKATEFgmhsBd0JEC0RoZq6QcauRxQAAAAAAB4AIHNhbHRAbm90YXRpb25zLnNl
cXVvaWEtcGdwLm9yZxDKeHct4SrN5lJ3oAkhIfwcJpCTVv9Sux05J7Pn0U6TApsg
vqAEGRYKAG8FgmhsBd0JEMb35fxT9XmfRxQAAAAAAB4AIHNhbHRAbm90YXRpb25z
LnNlcXVvaWEtcGdwLm9yZ9MZe54S5aYMdKLQmZiNN7Q1tot0zCuRp0DOMrZIsWQg
FiEEzXeihzhK/PSlCtVwxvfl/FP1eZ8AAFpUAQC1WlWjrTCL+ZiG3X9ThPO8418f
wu+p3l9jJAF1SK15QQEA6Go0+bbWOHMpkMNckSwlXhbBKVp53y2IhQnwLAfbZwoW
IQQfWCUoW3heHbE7820tEaGaukHGrgAAhYMA/iuXYUHqeXNpFCmoDFWmvwHDoPIs
8ZrgBJOfSnzg+x5wAQCFIWANcwYD/rCHTN6KQY70VI/x7SmkqKJZVrIBCB7DB844
BGhsBd0SCisGAQQBl1UBBQEBB0CYZYh5OKFAiuKOx4MIk6pocGCdfpL/XrJVoWjT
9aDSNAMBCAfCwAAEGBYKAHIFgmhsBd0JEC0RoZq6QcauRxQAAAAAAB4AIHNhbHRA
bm90YXRpb25zLnNlcXVvaWEtcGdwLm9yZ0oyJPZxXWc2dSxHpS1UAuvCfc80DaDy
mr1nRs5/QO0aApsMFiEEH1glKFt4Xh2xO/NtLRGhmrpBxq4AANKZAP0T00LyderN
Qsdk2UgpeeoZhN4wKtlUGocUs7I90P3AhgD/WuDXAlF6b9IXyTUoG9VkLrnlemCx
Dii+5qsdk0HFcgA=
=YS7U
-----END PGP PUBLIC KEY BLOCK-----

View File

@@ -3,4 +3,12 @@ package main
const (
// TestImageManifestDigest is the Docker manifest digest of "fixtures/image.manifest.json"
TestImageManifestDigest = "sha256:20bf21ed457b390829cdbeec8795a7bea1626991fda603e0d01b4e7f60427e55"
testSequoiaHome = "./fixtures"
// testSequoiaKeyFingerprint is a fingerprint of a test key in testSequoiaHome, generated using
// > sq --home $(pwd)/signature/simplesequoia/testdata key generate --name 'Skopeo Sequoia testing key' --own-key --expiration=never
testSequoiaKeyFingerprint = "50DDE898DF4E48755C8C2B7AF6F908B6FA48A229"
// testSequoiaKeyFingerprintWithPassphrase is a fingerprint of a test key in testSequoiaHome, generated using
// > sq --home $(pwd)/signature/simplesequoia/testdata key generate --name 'Skopeo Sequoia testing key with passphrase' --own-key --expiration=never
testSequoiaKeyFingerprintWithPassphrase = "1F5825285B785E1DB13BF36D2D11A19ABA41C6AE"
)

View File

@@ -13,8 +13,8 @@ import (
"testing"
"time"
"github.com/containers/storage/pkg/homedir"
"github.com/stretchr/testify/require"
"go.podman.io/storage/pkg/homedir"
)
var adminKUBECONFIG = map[string]string{
@@ -207,7 +207,7 @@ func (cluster *openshiftCluster) startRegistry(t *testing.T) {
cluster.processes = append(cluster.processes, cluster.startRegistryProcess(t, 5006, schema2Config))
}
// ocLogin runs (oc login) and (oc new-project) on the cluster, or terminates on failure.
// ocLoginToProject runs (oc login) and (oc new-project) on the cluster, or terminates on failure.
func (cluster *openshiftCluster) ocLoginToProject(t *testing.T) {
t.Logf("oc login")
cmd := cluster.clusterCmd(nil, "oc", "login", "--certificate-authority=openshift.local.config/master/ca.crt", "-u", "myuser", "-p", "mypw", "https://localhost:8443")
@@ -223,7 +223,7 @@ func (cluster *openshiftCluster) ocLoginToProject(t *testing.T) {
// We do not run (docker login) directly, because that requires a running daemon and a docker package.
func (cluster *openshiftCluster) dockerLogin(t *testing.T) {
cluster.dockerDir = filepath.Join(homedir.Get(), ".docker")
err := os.MkdirAll(cluster.dockerDir, 0700)
err := os.MkdirAll(cluster.dockerDir, 0o700)
require.NoError(t, err)
out := combinedOutputOfCommand(t, "oc", "config", "view", "-o", "json", "-o", "jsonpath={.users[*].user.token}")
@@ -237,7 +237,7 @@ func (cluster *openshiftCluster) dockerLogin(t *testing.T) {
}`, port, authValue))
}
configJSON := `{"auths": {` + strings.Join(auths, ",") + `}}`
err = os.WriteFile(filepath.Join(cluster.dockerDir, "config.json"), []byte(configJSON), 0600)
err = os.WriteFile(filepath.Join(cluster.dockerDir, "config.json"), []byte(configJSON), 0o600)
require.NoError(t, err)
}

View File

@@ -15,11 +15,11 @@ import (
"testing"
"time"
"github.com/containers/image/v5/manifest"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"go.podman.io/image/v5/manifest"
)
// This image is known to be x86_64 only right now
@@ -224,7 +224,6 @@ func (p *proxy) callGetRawBlob(args []any) (rval any, buf []byte, err error) {
content: buf,
err: err,
}
}()
wg.Add(1)
go func() {
@@ -282,7 +281,7 @@ func newProxy() (*proxy, error) {
}
// Note ExtraFiles starts at 3
proc := exec.Command("skopeo", "experimental-image-proxy", "--sockfd", "3")
proc := exec.Command(skopeoBinary, "experimental-image-proxy", "--sockfd", "3")
proc.Stderr = os.Stderr
cmdLifecycleToParentIfPossible(proc)
proc.ExtraFiles = append(proc.ExtraFiles, theirfd)

View File

@@ -70,7 +70,7 @@ compatibility:
username = "testuser"
password = "testpassword"
email = "test@test.org"
if err := os.WriteFile(htpasswdPath, []byte(userpasswd), os.FileMode(0644)); err != nil {
if err := os.WriteFile(htpasswdPath, []byte(userpasswd), os.FileMode(0o644)); err != nil {
return nil, err
}
htpasswd = fmt.Sprintf(`auth:

View File

@@ -1,16 +1,14 @@
package main
import (
"errors"
"fmt"
"os"
"os/exec"
"strings"
"testing"
"github.com/containers/image/v5/signature"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"go.podman.io/image/v5/signature"
)
const (
@@ -28,17 +26,6 @@ type signingSuite struct {
var _ = suite.SetupAllSuite(&signingSuite{})
func findFingerprint(lineBytes []byte) (string, error) {
lines := string(lineBytes)
for _, line := range strings.Split(lines, "\n") {
fields := strings.Split(line, ":")
if len(fields) >= 10 && fields[0] == "fpr" {
return fields[9], nil
}
}
return "", errors.New("No fingerprint found")
}
func (s *signingSuite) SetupSuite() {
t := s.T()
_, err := exec.LookPath(skopeoBinary)
@@ -57,7 +44,7 @@ func (s *signingSuite) SetupSuite() {
func (s *signingSuite) TestSignVerifySmoke() {
t := s.T()
mech, _, err := signature.NewEphemeralGPGSigningMechanism([]byte{})
mech, err := signature.NewGPGSigningMechanism()
require.NoError(t, err)
defer mech.Close()
if err := mech.SupportsSigning(); err != nil { // FIXME? Test that verification and policy enforcement works, using signatures from fixtures

View File

@@ -11,14 +11,14 @@ import (
"strings"
"testing"
"github.com/containers/image/v5/docker"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/types"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"go.podman.io/image/v5/docker"
"go.podman.io/image/v5/docker/reference"
"go.podman.io/image/v5/manifest"
"go.podman.io/image/v5/types"
)
const (
@@ -46,8 +46,10 @@ type syncSuite struct {
registry *testRegistryV2
}
var _ = suite.SetupAllSuite(&syncSuite{})
var _ = suite.TearDownAllSuite(&syncSuite{})
var (
_ = suite.SetupAllSuite(&syncSuite{})
_ = suite.TearDownAllSuite(&syncSuite{})
)
func (s *syncSuite) SetupSuite() {
t := s.T()
@@ -92,7 +94,7 @@ func (s *syncSuite) SetupSuite() {
out := combinedOutputOfCommand(t, gpgBinary, "--armor", "--export", fmt.Sprintf("%s@example.com", key))
err := os.WriteFile(filepath.Join(gpgHome, fmt.Sprintf("%s-pubkey.gpg", key)),
[]byte(out), 0600)
[]byte(out), 0o600)
require.NoError(t, err)
}
}
@@ -146,7 +148,7 @@ func (s *syncSuite) TestDocker2DirTagged() {
require.NoError(t, err)
// copy docker => dir
assertSkopeoSucceeds(t, "", "copy", "docker://"+image, "dir:"+dir2)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", "docker://"+image, "dir:"+dir2)
_, err = os.Stat(path.Join(dir2, "manifest.json"))
require.NoError(t, err)
@@ -173,7 +175,7 @@ func (s *syncSuite) TestDocker2DirTaggedAll() {
require.NoError(t, err)
// copy docker => dir
assertSkopeoSucceeds(t, "", "copy", "--all", "docker://"+image, "dir:"+dir2)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", "--all", "docker://"+image, "dir:"+dir2)
_, err = os.Stat(path.Join(dir2, "manifest.json"))
require.NoError(t, err)
@@ -189,11 +191,12 @@ func (s *syncSuite) TestPreserveDigests() {
image := pullableTaggedManifestList
// copy docker => dir
assertSkopeoSucceeds(t, "", "copy", "--all", "--preserve-digests", "docker://"+image, "dir:"+tmpDir)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", "--all", "--preserve-digests", "docker://"+image, "dir:"+tmpDir)
_, err := os.Stat(path.Join(tmpDir, "manifest.json"))
require.NoError(t, err)
assertSkopeoFails(t, ".*Instructed to preserve digests.*", "copy", "--all", "--preserve-digests", "--format=oci", "docker://"+image, "dir:"+tmpDir)
assertSkopeoFails(t, ".*Instructed to preserve digests.*",
"copy", "--retry-times", "3", "--all", "--preserve-digests", "--format=oci", "docker://"+image, "dir:"+tmpDir)
}
func (s *syncSuite) TestScoped() {
@@ -223,18 +226,18 @@ func (s *syncSuite) TestDirIsNotOverwritten() {
imagePath := imageRef.DockerReference().String()
// make a copy of the image in the local registry
assertSkopeoSucceeds(t, "", "copy", "--dest-tls-verify=false", "docker://"+image, "docker://"+path.Join(v2DockerRegistryURL, reference.Path(imageRef.DockerReference())))
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", "--dest-tls-verify=false", "docker://"+image, "docker://"+path.Join(v2DockerRegistryURL, reference.Path(imageRef.DockerReference())))
//sync upstream image to dir, not scoped
// sync upstream image to dir, not scoped
dir1 := t.TempDir()
assertSkopeoSucceeds(t, "", "sync", "--src", "docker", "--dest", "dir", image, dir1)
_, err = os.Stat(path.Join(dir1, path.Base(imagePath), "manifest.json"))
require.NoError(t, err)
//sync local registry image to dir, not scoped
// sync local registry image to dir, not scoped
assertSkopeoFails(t, ".*Refusing to overwrite destination directory.*", "sync", "--src-tls-verify=false", "--src", "docker", "--dest", "dir", path.Join(v2DockerRegistryURL, reference.Path(imageRef.DockerReference())), dir1)
//sync local registry image to dir, scoped
// sync local registry image to dir, scoped
imageRef, err = docker.ParseReference(fmt.Sprintf("//%s", path.Join(v2DockerRegistryURL, reference.Path(imageRef.DockerReference()))))
require.NoError(t, err)
imagePath = imageRef.DockerReference().String()
@@ -289,7 +292,7 @@ func (s *syncSuite) TestYamlUntagged() {
// sync to the local registry
yamlFile := path.Join(tmpDir, "registries.yaml")
err = os.WriteFile(yamlFile, []byte(yamlConfig), 0644)
err = os.WriteFile(yamlFile, []byte(yamlConfig), 0o644)
require.NoError(t, err)
assertSkopeoSucceeds(t, "", "sync", "--scoped", "--src", "yaml", "--dest", "docker", "--dest-tls-verify=false", yamlFile, v2DockerRegistryURL)
// sync back from local registry to a folder
@@ -301,7 +304,7 @@ func (s *syncSuite) TestYamlUntagged() {
%s: []
`, v2DockerRegistryURL, imagePath)
err = os.WriteFile(yamlFile, []byte(yamlConfig), 0644)
err = os.WriteFile(yamlFile, []byte(yamlConfig), 0o644)
require.NoError(t, err)
assertSkopeoSucceeds(t, "", "sync", "--scoped", "--src", "yaml", "--dest", "dir", yamlFile, dir1)
@@ -328,11 +331,11 @@ registry.k8s.io:
pause: ^[12]\.0$ # regex string test
`
// the ↑ regex strings always matches only 2 images
var nTags = 2
nTags := 2
assert.NotZero(t, nTags)
yamlFile := path.Join(tmpDir, "registries.yaml")
err := os.WriteFile(yamlFile, []byte(yamlConfig), 0644)
err := os.WriteFile(yamlFile, []byte(yamlConfig), 0o644)
require.NoError(t, err)
assertSkopeoSucceeds(t, "", "sync", "--scoped", "--src", "yaml", "--dest", "dir", yamlFile, dir1)
assertNumberOfManifestsInSubdirs(t, dir1, nTags)
@@ -350,7 +353,7 @@ registry.k8s.io:
- sha256:59eec8837a4d942cc19a52b8c09ea75121acc38114a2c68b98983ce9356b8610
`
yamlFile := path.Join(tmpDir, "registries.yaml")
err := os.WriteFile(yamlFile, []byte(yamlConfig), 0644)
err := os.WriteFile(yamlFile, []byte(yamlConfig), 0o644)
require.NoError(t, err)
assertSkopeoSucceeds(t, "", "sync", "--scoped", "--src", "yaml", "--dest", "dir", yamlFile, dir1)
assertNumberOfManifestsInSubdirs(t, dir1, 1)
@@ -381,7 +384,7 @@ quay.io:
// get the number of tags
re := regexp.MustCompile(`^ +- +[^:/ ]+`)
var nTags int
for _, l := range strings.Split(yamlConfig, "\n") {
for l := range strings.SplitSeq(yamlConfig, "\n") {
if re.MatchString(l) {
nTags++
}
@@ -389,7 +392,7 @@ quay.io:
assert.NotZero(t, nTags)
yamlFile := path.Join(tmpDir, "registries.yaml")
err := os.WriteFile(yamlFile, []byte(yamlConfig), 0644)
err := os.WriteFile(yamlFile, []byte(yamlConfig), 0o644)
require.NoError(t, err)
assertSkopeoSucceeds(t, "", "sync", "--scoped", "--src", "yaml", "--dest", "dir", yamlFile, dir1)
assertNumberOfManifestsInSubdirs(t, dir1, nTags)
@@ -405,7 +408,7 @@ func (s *syncSuite) TestYamlTLSVerify() {
// FIXME: It would be nice to use one of the local Docker registries instead of needing an Internet connection.
// copy docker => docker
assertSkopeoSucceeds(t, "", "copy", "--dest-tls-verify=false", "docker://"+image+":"+tag, localRegURL+image+":"+tag)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", "--dest-tls-verify=false", "docker://"+image+":"+tag, localRegURL+image+":"+tag)
yamlTemplate := `
%s:
@@ -440,14 +443,13 @@ func (s *syncSuite) TestYamlTLSVerify() {
for _, cfg := range testCfg {
yamlConfig := fmt.Sprintf(yamlTemplate, v2DockerRegistryURL, cfg.tlsVerify, image, tag)
yamlFile := path.Join(tmpDir, "registries.yaml")
err := os.WriteFile(yamlFile, []byte(yamlConfig), 0644)
err := os.WriteFile(yamlFile, []byte(yamlConfig), 0o644)
require.NoError(t, err)
cfg.checker(t, cfg.msg, "sync", "--scoped", "--src", "yaml", "--dest", "dir", yamlFile, dir1)
os.Remove(yamlFile)
os.RemoveAll(dir1)
}
}
func (s *syncSuite) TestSyncManifestOutput() {
@@ -458,7 +460,7 @@ func (s *syncSuite) TestSyncManifestOutput() {
destDir2 := filepath.Join(tmpDir, "dest2")
destDir3 := filepath.Join(tmpDir, "dest3")
//Split image:tag path from image URI for manifest comparison
// Split image:tag path from image URI for manifest comparison
imageDir := pullableTaggedImage[strings.LastIndex(pullableTaggedImage, "/")+1:]
assertSkopeoSucceeds(t, "", "sync", "--format=oci", "--all", "--src", "docker", "--dest", "dir", pullableTaggedImage, destDir1)
@@ -488,7 +490,7 @@ func (s *syncSuite) TestDocker2DockerTagged() {
assertSkopeoSucceeds(t, "", "sync", "--scoped", "--dest-tls-verify=false", "--src", "docker", "--dest", "docker", image, v2DockerRegistryURL)
// copy docker => dir
assertSkopeoSucceeds(t, "", "copy", "docker://"+image, "dir:"+dir1)
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", "docker://"+image, "dir:"+dir1)
_, err = os.Stat(path.Join(dir1, "manifest.json"))
require.NoError(t, err)
@@ -511,18 +513,18 @@ func (s *syncSuite) TestDir2DockerTagged() {
image := pullableRepoWithLatestTag
dir1 := path.Join(tmpDir, "dir1")
err := os.Mkdir(dir1, 0755)
err := os.Mkdir(dir1, 0o755)
require.NoError(t, err)
dir2 := path.Join(tmpDir, "dir2")
err = os.Mkdir(dir2, 0755)
err = os.Mkdir(dir2, 0o755)
require.NoError(t, err)
// create leading dirs
err = os.MkdirAll(path.Dir(path.Join(dir1, image)), 0755)
err = os.MkdirAll(path.Dir(path.Join(dir1, image)), 0o755)
require.NoError(t, err)
// copy docker => dir
assertSkopeoSucceeds(t, "", "copy", "docker://"+image, "dir:"+path.Join(dir1, image))
assertSkopeoSucceeds(t, "", "copy", "--retry-times", "3", "docker://"+image, "dir:"+path.Join(dir1, image))
_, err = os.Stat(path.Join(dir1, image, "manifest.json"))
require.NoError(t, err)
@@ -530,7 +532,7 @@ func (s *syncSuite) TestDir2DockerTagged() {
assertSkopeoSucceeds(t, "", "sync", "--scoped", "--dest-tls-verify=false", "--src", "dir", "--dest", "docker", dir1, v2DockerRegistryURL)
// create leading dirs
err = os.MkdirAll(path.Dir(path.Join(dir2, image)), 0755)
err = os.MkdirAll(path.Dir(path.Join(dir2, image)), 0o755)
require.NoError(t, err)
// copy docker => dir
@@ -570,11 +572,11 @@ func (s *syncSuite) TestFailsWithDockerSourceNoRegistry() {
tmpDir := t.TempDir()
//untagged
// untagged
assertSkopeoFails(t, ".*StatusCode: 404.*",
"sync", "--scoped", "--src", "docker", "--dest", "dir", regURL, tmpDir)
//tagged
// tagged
assertSkopeoFails(t, ".*StatusCode: 404.*",
"sync", "--scoped", "--src", "docker", "--dest", "dir", regURL+":thetag", tmpDir)
}
@@ -584,11 +586,11 @@ func (s *syncSuite) TestFailsWithDockerSourceUnauthorized() {
const repo = "privateimagenamethatshouldnotbepublic"
tmpDir := t.TempDir()
//untagged
// untagged
assertSkopeoFails(t, ".*requested access to the resource is denied.*",
"sync", "--scoped", "--src", "docker", "--dest", "dir", repo, tmpDir)
//tagged
// tagged
assertSkopeoFails(t, ".*requested access to the resource is denied.*",
"sync", "--scoped", "--src", "docker", "--dest", "dir", repo+":thetag", tmpDir)
}
@@ -598,11 +600,11 @@ func (s *syncSuite) TestFailsWithDockerSourceNotExisting() {
repo := path.Join(v2DockerRegistryURL, "imagedoesnotexist")
tmpDir := t.TempDir()
//untagged
// untagged
assertSkopeoFails(t, ".*repository name not known to registry.*",
"sync", "--scoped", "--src-tls-verify=false", "--src", "docker", "--dest", "dir", repo, tmpDir)
//tagged
// tagged
assertSkopeoFails(t, ".*reading manifest.*",
"sync", "--scoped", "--src-tls-verify=false", "--src", "docker", "--dest", "dir", repo+":thetag", tmpDir)
}

View File

@@ -0,0 +1,114 @@
package main
import (
"net/http"
"net/http/httptest"
"slices"
"strings"
"sync"
"testing"
"github.com/stretchr/testify/require"
)
// mockRegistryHandler implements a minimal Docker Registry V2 API that captures User-Agent headers
type mockRegistryHandler struct {
mu sync.Mutex
userAgents []string
}
func (h *mockRegistryHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Capture the User-Agent header
h.mu.Lock()
h.userAgents = append(h.userAgents, r.Header.Get("User-Agent"))
h.mu.Unlock()
// Implement minimal Docker Registry V2 API endpoints for inspect --raw
switch {
case r.URL.Path == "/v2/":
// Registry version check endpoint
w.Header().Set("Docker-Distribution-API-Version", "registry/2.0")
w.WriteHeader(http.StatusOK)
case strings.HasSuffix(r.URL.Path, "/manifests/latest"):
// Return a minimal OCI manifest as raw string
// The digest matches this exact content
manifest := `{"schemaVersion":2,"mediaType":"application/vnd.oci.image.manifest.v1+json","config":{"mediaType":"application/vnd.oci.image.config.v1+json","digest":"sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a","size":2},"layers":[{"mediaType":"application/vnd.oci.image.layer.v1.tar+gzip","digest":"sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","size":0}]}`
w.Header().Set("Content-Type", "application/vnd.oci.image.manifest.v1+json")
w.WriteHeader(http.StatusOK)
if _, err := w.Write([]byte(manifest)); err != nil {
panic(err)
}
default:
w.WriteHeader(http.StatusNotFound)
}
}
func (h *mockRegistryHandler) getUserAgents() []string {
h.mu.Lock()
defer h.mu.Unlock()
return slices.Clone(h.userAgents)
}
func TestUserAgent(t *testing.T) {
testCases := []struct {
name string
extraArgs []string
userAgentValidator func(string) bool
description string
}{
{
name: "default user agent",
extraArgs: []string{},
userAgentValidator: func(ua string) bool {
return strings.HasPrefix(ua, "skopeo/")
},
description: "Default user agent should start with 'skopeo/'",
},
{
name: "custom user agent prefix",
extraArgs: []string{"--user-agent-prefix", "bootc/1.0"},
userAgentValidator: func(ua string) bool {
return strings.HasPrefix(ua, "bootc/1.0 skopeo/")
},
description: "Custom user agent should be in format 'prefix skopeo/version'",
},
{
name: "prefix with spaces",
extraArgs: []string{"--user-agent-prefix", "my cool app"},
userAgentValidator: func(ua string) bool {
return strings.HasPrefix(ua, "my cool app skopeo/")
},
description: "User agent with spaces should work correctly",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
handler := &mockRegistryHandler{}
server := httptest.NewServer(handler)
defer server.Close()
// Extract host:port from the test server URL
registryAddr := strings.TrimPrefix(server.URL, "http://")
imageRef := "docker://" + registryAddr + "/test/image:latest"
// Build arguments: base args + test-specific args + image ref
args := append([]string{"--tls-verify=false"}, tc.extraArgs...)
args = append(args, "inspect", "--raw", imageRef)
// Run skopeo inspect --raw
assertSkopeoSucceeds(t, "", args...)
// Verify that at least one request was made with the expected User-Agent
userAgents := handler.getUserAgents()
require.NotEmpty(t, userAgents, "Expected at least one request to be made")
// Check that at least one User-Agent matches the validator
require.True(t,
slices.ContainsFunc(userAgents, tc.userAgentValidator),
"%s, got: %v", tc.description, userAgents)
})
}
}

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"compress/gzip"
"encoding/json"
"errors"
"io"
"net"
"net/netip"
@@ -14,17 +15,37 @@ import (
"testing"
"time"
"github.com/containers/image/v5/manifest"
"github.com/opencontainers/go-digest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.podman.io/image/v5/manifest"
)
const skopeoBinary = "skopeo"
// FIXME: Move to SetupSuite
// https://github.com/containers/skopeo/pull/2703#discussion_r2331374730
var skopeoBinary = func() string {
if binary := os.Getenv("SKOPEO_BINARY"); binary != "" {
return binary
}
return "skopeo"
}()
const testFQIN = "docker://quay.io/libpod/busybox" // tag left off on purpose, some tests need to add a special one
const testFQIN64 = "docker://quay.io/libpod/busybox:amd64"
const testFQINMultiLayer = "docker://quay.io/libpod/alpine_nginx:latest" // multi-layer
// findFingerprint extracts the GPG key fingerprint from gpg --with-colons output.
func findFingerprint(lineBytes []byte) (string, error) {
for line := range bytes.SplitSeq(lineBytes, []byte{'\n'}) {
fields := strings.Split(string(line), ":")
if len(fields) >= 10 && fields[0] == "fpr" {
return fields[9], nil
}
}
return "", errors.New("No fingerprint found")
}
const (
testFQIN = "docker://quay.io/libpod/busybox" // tag left off on purpose, some tests need to add a special one
testFQIN64 = "docker://quay.io/libpod/busybox:amd64"
testFQINMultiLayer = "docker://quay.io/libpod/alpine_nginx:latest" // multi-layer
)
// consumeAndLogOutputStream takes (f, err) from an exec.*Pipe(), and causes all output to it to be logged to t.
func consumeAndLogOutputStream(t *testing.T, id string, f io.ReadCloser, err error) {

View File

@@ -24,3 +24,11 @@ prepare:
fi
dnf -y upgrade --allowerasing
order: 20
- name: Disable installing everything from srpm
how: install
exclude:
- ".*"
- name: Install the main package
how: install
package:
- skopeo-tests

View File

@@ -1,16 +0,0 @@
--- !Policy
product_versions:
- fedora-*
decision_contexts:
- bodhi_update_push_stable
- bodhi_update_push_testing
subject_type: koji_build
rules:
- !PassingTestCaseRule {test_case_name: fedora-ci.koji-build.tier0.functional}
--- !Policy
product_versions:
- rhel-*
decision_context: osci_compose_gate
rules:
- !PassingTestCaseRule {test_case_name: osci.brew-build.tier0.functional}

View File

@@ -9,26 +9,27 @@
%global gomodulesmode GO111MODULE=on
# No btrfs on RHEL
# Distro and environment conditionals
%if %{defined fedora}
# Fedora conditionals
%define build_with_btrfs 1
%define conditional_epoch 1
%if %{?fedora} >= 43
%define sequoia 1
%endif
%if %{defined rhel}
%else
# RHEL conditionals
%define conditional_epoch 2
%define fips 1
%endif
# Only used in official koji builds
# Copr builds set a separate epoch for all environments
%if %{defined fedora}
%define conditional_epoch 1
%define fakeroot 1
%else
%define conditional_epoch 2
# set higher Epoch only for podman-next builds
%if %{defined copr_username} && "%{copr_username}" == "rhcontainerbot" && "%{copr_projectname}" == "podman-next"
%define next_build 1
%endif
Name: skopeo
%if %{defined copr_username}
%if %{defined next_build}
Epoch: 102
%else
Epoch: %{conditional_epoch}
@@ -68,20 +69,20 @@ BuildRequires: make
BuildRequires: shadow-utils-subid-devel
BuildRequires: sqlite-devel
Requires: containers-common >= 4:1-21
%if %{defined sequoia}
Requires: podman-sequoia
%endif
%description
Command line utility to inspect images and repositories directly on Docker
registries without the need to pull them
registries without the need to pull them.
# NOTE: The tests subpackage is only intended for testing and will not be supported
# for end-users and/or customers.
%package tests
Summary: Tests for %{name}
Summary: Test dependencies for %{name}
Requires: %{name} = %{epoch}:%{version}-%{release}
%if %{defined fakeroot}
Requires: fakeroot
%endif
Requires: gnupg
Requires: jq
Requires: golang
@@ -90,13 +91,12 @@ Requires: crun
Requires: httpd-tools
Requires: openssl
Requires: squashfs-tools
# bats is not present on RHEL and ELN so it shouldn't be a strong dep
# bats and fakeroot are not present on RHEL and ELN so they shouldn't be strong deps
Recommends: bats
Recommends: fakeroot
%description tests
%{summary}
This package contains system tests for %{name}
This package installs system test dependencies for %{name}
%prep
%autosetup -Sgit %{name}-%{version}
@@ -130,6 +130,10 @@ export BUILDTAGS="$BASEBUILDTAGS exclude_graphdriver_btrfs"
export BUILDTAGS="$BUILDTAGS libtrust_openssl"
%endif
%if %{defined sequoia}
export BUILDTAGS="$BUILDTAGS containers_image_sequoia"
%endif
# unset LDFLAGS earlier set from set_build_flags
LDFLAGS=''
@@ -142,10 +146,6 @@ make \
PREFIX=%{_prefix} \
install-binary install-docs install-completions
# system tests
install -d -p %{buildroot}/%{_datadir}/%{name}/test/system
cp -pav systemtest/* %{buildroot}/%{_datadir}/%{name}/test/system/
#define license tag if not already defined
%{!?_licensedir:%global license %doc}
@@ -166,9 +166,8 @@ cp -pav systemtest/* %{buildroot}/%{_datadir}/%{name}/test/system/
%dir %{_datadir}/zsh/site-functions
%{_datadir}/zsh/site-functions/_%{name}
# Only test dependencies installed, no files.
%files tests
%license LICENSE vendor/modules.txt
%{_datadir}/%{name}/test
%changelog
%autochangelog

View File

@@ -136,6 +136,8 @@ END_PUSH
# Done pushing. Now try to fetch. From here on we use the --policy option.
# The table below lists the paths to fetch, and the expected errors (or
# none, if we expect them to pass).
#
# "Invalid GPG signature" is reported by the gpgme mechanism; "Missing key: $fingerprint" or "Missing key $fingerprint" by Sequoia.
while read path expected_error; do
expected_rc=
if [[ -n $expected_error ]]; then
@@ -154,7 +156,7 @@ END_PUSH
fi
done <<END_TESTS
/myns/alice:signed
/myns/bob:signedbyalice Invalid GPG signature
/myns/bob:signedbyalice (Invalid GPG signature|Missing key)
/myns/alice:unsigned Signature for identity \\\\\\\\"localhost:5000/myns/alice:signed\\\\\\\\" is not accepted
/myns/carol:latest Running image docker://localhost:5000/myns/carol:latest is rejected by policy.
/open/forall:latest

View File

@@ -1,6 +1,3 @@
require:
- skopeo-tests
environment:
SKOPEO_BINARY: /usr/bin/skopeo
@@ -10,5 +7,13 @@ adjust:
RELEASE_TESTING: true
summary: System test
test: bash ./test.sh
test: |
rpm -q containers-common skopeo-tests
# If these tests are to be run anywhere else, TMT can fetch
# them using the URL and git ref, so git commands should work.
make -C $(git rev-parse --show-toplevel) test-system-local
require:
- git-core
- make
- skopeo-tests
duration: 60m

View File

@@ -1,13 +0,0 @@
#!/usr/bin/env bash
set -exo pipefail
uname -r
rpm -q \
bats \
containers-common \
skopeo \
skopeo-tests \
bats /usr/share/skopeo/test/system

43
vendor/cyphar.com/go-pathrs/.golangci.yml generated vendored Normal file
View File

@@ -0,0 +1,43 @@
# SPDX-License-Identifier: MPL-2.0
#
# libpathrs: safe path resolution on Linux
# Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
# Copyright (C) 2019-2025 SUSE LLC
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
version: "2"
linters:
enable:
- bidichk
- cyclop
- errname
- errorlint
- exhaustive
- goconst
- godot
- gomoddirectives
- gosec
- mirror
- misspell
- mnd
- nilerr
- nilnil
- perfsprint
- prealloc
- reassign
- revive
- unconvert
- unparam
- usestdlibvars
- wastedassign
formatters:
enable:
- gofumpt
- goimports
settings:
goimports:
local-prefixes:
- cyphar.com/go-pathrs

View File

@@ -1,5 +1,3 @@
Copyright 2016 ISRG. All rights reserved.
Mozilla Public License Version 2.0
==================================
@@ -37,7 +35,7 @@ Mozilla Public License Version 2.0
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
@@ -359,7 +357,7 @@ Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
file, You can obtain one at https://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE

14
vendor/cyphar.com/go-pathrs/doc.go generated vendored Normal file
View File

@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MPL-2.0
/*
* libpathrs: safe path resolution on Linux
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
* Copyright (C) 2019-2025 SUSE LLC
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
// Package pathrs provides bindings for libpathrs, a library for safe path
// resolution on Linux.
package pathrs

114
vendor/cyphar.com/go-pathrs/handle_linux.go generated vendored Normal file
View File

@@ -0,0 +1,114 @@
//go:build linux
// SPDX-License-Identifier: MPL-2.0
/*
* libpathrs: safe path resolution on Linux
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
* Copyright (C) 2019-2025 SUSE LLC
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package pathrs
import (
"fmt"
"os"
"cyphar.com/go-pathrs/internal/fdutils"
"cyphar.com/go-pathrs/internal/libpathrs"
)
// Handle is a handle for a path within a given [Root]. This handle references
// an already-resolved path which can be used for only one purpose -- to
// "re-open" the handle and get an actual [os.File] which can be used for
// ordinary operations.
//
// If you wish to open a file without having an intermediate [Handle] object,
// you can try to use [Root.Open] or [Root.OpenFile].
//
// It is critical that perform all relevant operations through this [Handle]
// (rather than fetching the file descriptor yourself with [Handle.IntoRaw]),
// because the security properties of libpathrs depend on users doing all
// relevant filesystem operations through libpathrs.
//
// [os.File]: https://pkg.go.dev/os#File
type Handle struct {
inner *os.File
}
// HandleFromFile creates a new [Handle] from an existing file handle. The
// handle will be copied by this method, so the original handle should still be
// freed by the caller.
//
// This is effectively the inverse operation of [Handle.IntoRaw], and is used
// for "deserialising" pathrs root handles.
func HandleFromFile(file *os.File) (*Handle, error) {
newFile, err := fdutils.DupFile(file)
if err != nil {
return nil, fmt.Errorf("duplicate handle fd: %w", err)
}
return &Handle{inner: newFile}, nil
}
// Open creates an "upgraded" file handle to the file referenced by the
// [Handle]. Note that the original [Handle] is not consumed by this operation,
// and can be opened multiple times.
//
// The handle returned is only usable for reading, and this is method is
// shorthand for [Handle.OpenFile] with os.O_RDONLY.
//
// TODO: Rename these to "Reopen" or something.
func (h *Handle) Open() (*os.File, error) {
return h.OpenFile(os.O_RDONLY)
}
// OpenFile creates an "upgraded" file handle to the file referenced by the
// [Handle]. Note that the original [Handle] is not consumed by this operation,
// and can be opened multiple times.
//
// The provided flags indicate which open(2) flags are used to create the new
// handle.
//
// TODO: Rename these to "Reopen" or something.
func (h *Handle) OpenFile(flags int) (*os.File, error) {
return fdutils.WithFileFd(h.inner, func(fd uintptr) (*os.File, error) {
newFd, err := libpathrs.Reopen(fd, flags)
if err != nil {
return nil, err
}
return os.NewFile(newFd, h.inner.Name()), nil
})
}
// IntoFile unwraps the [Handle] into its underlying [os.File].
//
// You almost certainly want to use [Handle.OpenFile] to get a non-O_PATH
// version of this [Handle].
//
// This operation returns the internal [os.File] of the [Handle] directly, so
// calling [Handle.Close] will also close any copies of the returned [os.File].
// If you want to get an independent copy, use [Handle.Clone] followed by
// [Handle.IntoFile] on the cloned [Handle].
//
// [os.File]: https://pkg.go.dev/os#File
func (h *Handle) IntoFile() *os.File {
// TODO: Figure out if we really don't want to make a copy.
// TODO: We almost certainly want to clear r.inner here, but we can't do
// that easily atomically (we could use atomic.Value but that'll make
// things quite a bit uglier).
return h.inner
}
// Clone creates a copy of a [Handle], such that it has a separate lifetime to
// the original (while referring to the same underlying file).
func (h *Handle) Clone() (*Handle, error) {
return HandleFromFile(h.inner)
}
// Close frees all of the resources used by the [Handle].
func (h *Handle) Close() error {
return h.inner.Close()
}

View File

@@ -0,0 +1,75 @@
//go:build linux
// SPDX-License-Identifier: MPL-2.0
/*
* libpathrs: safe path resolution on Linux
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
* Copyright (C) 2019-2025 SUSE LLC
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
// Package fdutils contains a few helper methods when dealing with *os.File and
// file descriptors.
package fdutils
import (
"fmt"
"os"
"golang.org/x/sys/unix"
"cyphar.com/go-pathrs/internal/libpathrs"
)
// DupFd makes a duplicate of the given fd.
func DupFd(fd uintptr, name string) (*os.File, error) {
newFd, err := unix.FcntlInt(fd, unix.F_DUPFD_CLOEXEC, 0)
if err != nil {
return nil, fmt.Errorf("fcntl(F_DUPFD_CLOEXEC): %w", err)
}
return os.NewFile(uintptr(newFd), name), nil
}
// WithFileFd is a more ergonomic wrapper around file.SyscallConn().Control().
func WithFileFd[T any](file *os.File, fn func(fd uintptr) (T, error)) (T, error) {
conn, err := file.SyscallConn()
if err != nil {
return *new(T), err
}
var (
ret T
innerErr error
)
if err := conn.Control(func(fd uintptr) {
ret, innerErr = fn(fd)
}); err != nil {
return *new(T), err
}
return ret, innerErr
}
// DupFile makes a duplicate of the given file.
func DupFile(file *os.File) (*os.File, error) {
return WithFileFd(file, func(fd uintptr) (*os.File, error) {
return DupFd(fd, file.Name())
})
}
// MkFile creates a new *os.File from the provided file descriptor. However,
// unlike os.NewFile, the file's Name is based on the real path (provided by
// /proc/self/fd/$n).
func MkFile(fd uintptr) (*os.File, error) {
fdPath := fmt.Sprintf("fd/%d", fd)
fdName, err := libpathrs.ProcReadlinkat(libpathrs.ProcDefaultRootFd, libpathrs.ProcThreadSelf, fdPath)
if err != nil {
_ = unix.Close(int(fd))
return nil, fmt.Errorf("failed to fetch real name of fd %d: %w", fd, err)
}
// TODO: Maybe we should prefix this name with something to indicate to
// users that they must not use this path as a "safe" path. Something like
// "//pathrs-handle:/foo/bar"?
return os.NewFile(fd, fdName), nil
}

View File

@@ -0,0 +1,40 @@
//go:build linux
// TODO: Use "go:build unix" once we bump the minimum Go version 1.19.
// SPDX-License-Identifier: MPL-2.0
/*
* libpathrs: safe path resolution on Linux
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
* Copyright (C) 2019-2025 SUSE LLC
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package libpathrs
import (
"syscall"
)
// Error represents an underlying libpathrs error.
type Error struct {
description string
errno syscall.Errno
}
// Error returns a textual description of the error.
func (err *Error) Error() string {
return err.description
}
// Unwrap returns the underlying error which was wrapped by this error (if
// applicable).
func (err *Error) Unwrap() error {
if err.errno != 0 {
return err.errno
}
return nil
}

View File

@@ -0,0 +1,337 @@
//go:build linux
// SPDX-License-Identifier: MPL-2.0
/*
* libpathrs: safe path resolution on Linux
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
* Copyright (C) 2019-2025 SUSE LLC
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
// Package libpathrs is an internal thin wrapper around the libpathrs C API.
package libpathrs
import (
"fmt"
"syscall"
"unsafe"
)
/*
// TODO: Figure out if we need to add support for linking against libpathrs
// statically even if in dynamically linked builds in order to make
// packaging a bit easier (using "-Wl,-Bstatic -lpathrs -Wl,-Bdynamic" or
// "-l:pathrs.a").
#cgo pkg-config: pathrs
#include <pathrs.h>
// This is a workaround for unsafe.Pointer() not working for non-void pointers.
char *cast_ptr(void *ptr) { return ptr; }
*/
import "C"
func fetchError(errID C.int) error {
if errID >= C.__PATHRS_MAX_ERR_VALUE {
return nil
}
cErr := C.pathrs_errorinfo(errID)
defer C.pathrs_errorinfo_free(cErr)
var err error
if cErr != nil {
err = &Error{
errno: syscall.Errno(cErr.saved_errno),
description: C.GoString(cErr.description),
}
}
return err
}
// OpenRoot wraps pathrs_open_root.
func OpenRoot(path string) (uintptr, error) {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
fd := C.pathrs_open_root(cPath)
return uintptr(fd), fetchError(fd)
}
// Reopen wraps pathrs_reopen.
func Reopen(fd uintptr, flags int) (uintptr, error) {
newFd := C.pathrs_reopen(C.int(fd), C.int(flags))
return uintptr(newFd), fetchError(newFd)
}
// InRootResolve wraps pathrs_inroot_resolve.
func InRootResolve(rootFd uintptr, path string) (uintptr, error) {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
fd := C.pathrs_inroot_resolve(C.int(rootFd), cPath)
return uintptr(fd), fetchError(fd)
}
// InRootResolveNoFollow wraps pathrs_inroot_resolve_nofollow.
func InRootResolveNoFollow(rootFd uintptr, path string) (uintptr, error) {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
fd := C.pathrs_inroot_resolve_nofollow(C.int(rootFd), cPath)
return uintptr(fd), fetchError(fd)
}
// InRootOpen wraps pathrs_inroot_open.
func InRootOpen(rootFd uintptr, path string, flags int) (uintptr, error) {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
fd := C.pathrs_inroot_open(C.int(rootFd), cPath, C.int(flags))
return uintptr(fd), fetchError(fd)
}
// InRootReadlink wraps pathrs_inroot_readlink.
func InRootReadlink(rootFd uintptr, path string) (string, error) {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
size := 128
for {
linkBuf := make([]byte, size)
n := C.pathrs_inroot_readlink(C.int(rootFd), cPath, C.cast_ptr(unsafe.Pointer(&linkBuf[0])), C.ulong(len(linkBuf)))
switch {
case int(n) < C.__PATHRS_MAX_ERR_VALUE:
return "", fetchError(n)
case int(n) <= len(linkBuf):
return string(linkBuf[:int(n)]), nil
default:
// The contents were truncated. Unlike readlinkat, pathrs returns
// the size of the link when it checked. So use the returned size
// as a basis for the reallocated size (but in order to avoid a DoS
// where a magic-link is growing by a single byte each iteration,
// make sure we are a fair bit larger).
size += int(n)
}
}
}
// InRootRmdir wraps pathrs_inroot_rmdir.
func InRootRmdir(rootFd uintptr, path string) error {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
err := C.pathrs_inroot_rmdir(C.int(rootFd), cPath)
return fetchError(err)
}
// InRootUnlink wraps pathrs_inroot_unlink.
func InRootUnlink(rootFd uintptr, path string) error {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
err := C.pathrs_inroot_unlink(C.int(rootFd), cPath)
return fetchError(err)
}
// InRootRemoveAll wraps pathrs_inroot_remove_all.
func InRootRemoveAll(rootFd uintptr, path string) error {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
err := C.pathrs_inroot_remove_all(C.int(rootFd), cPath)
return fetchError(err)
}
// InRootCreat wraps pathrs_inroot_creat.
func InRootCreat(rootFd uintptr, path string, flags int, mode uint32) (uintptr, error) {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
fd := C.pathrs_inroot_creat(C.int(rootFd), cPath, C.int(flags), C.uint(mode))
return uintptr(fd), fetchError(fd)
}
// InRootRename wraps pathrs_inroot_rename.
func InRootRename(rootFd uintptr, src, dst string, flags uint) error {
cSrc := C.CString(src)
defer C.free(unsafe.Pointer(cSrc))
cDst := C.CString(dst)
defer C.free(unsafe.Pointer(cDst))
err := C.pathrs_inroot_rename(C.int(rootFd), cSrc, cDst, C.uint(flags))
return fetchError(err)
}
// InRootMkdir wraps pathrs_inroot_mkdir.
func InRootMkdir(rootFd uintptr, path string, mode uint32) error {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
err := C.pathrs_inroot_mkdir(C.int(rootFd), cPath, C.uint(mode))
return fetchError(err)
}
// InRootMkdirAll wraps pathrs_inroot_mkdir_all.
func InRootMkdirAll(rootFd uintptr, path string, mode uint32) (uintptr, error) {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
fd := C.pathrs_inroot_mkdir_all(C.int(rootFd), cPath, C.uint(mode))
return uintptr(fd), fetchError(fd)
}
// InRootMknod wraps pathrs_inroot_mknod.
func InRootMknod(rootFd uintptr, path string, mode uint32, dev uint64) error {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
err := C.pathrs_inroot_mknod(C.int(rootFd), cPath, C.uint(mode), C.dev_t(dev))
return fetchError(err)
}
// InRootSymlink wraps pathrs_inroot_symlink.
func InRootSymlink(rootFd uintptr, path, target string) error {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
cTarget := C.CString(target)
defer C.free(unsafe.Pointer(cTarget))
err := C.pathrs_inroot_symlink(C.int(rootFd), cPath, cTarget)
return fetchError(err)
}
// InRootHardlink wraps pathrs_inroot_hardlink.
func InRootHardlink(rootFd uintptr, path, target string) error {
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
cTarget := C.CString(target)
defer C.free(unsafe.Pointer(cTarget))
err := C.pathrs_inroot_hardlink(C.int(rootFd), cPath, cTarget)
return fetchError(err)
}
// ProcBase is pathrs_proc_base_t (uint64_t).
type ProcBase C.pathrs_proc_base_t
// FIXME: We need to open-code the constants because CGo unfortunately will
// implicitly convert any non-literal constants (i.e. those resolved using gcc)
// to signed integers. See <https://github.com/golang/go/issues/39136> for some
// more information on the underlying issue (though.
const (
// ProcRoot is PATHRS_PROC_ROOT.
ProcRoot ProcBase = 0xFFFF_FFFE_7072_6F63 // C.PATHRS_PROC_ROOT
// ProcSelf is PATHRS_PROC_SELF.
ProcSelf ProcBase = 0xFFFF_FFFE_091D_5E1F // C.PATHRS_PROC_SELF
// ProcThreadSelf is PATHRS_PROC_THREAD_SELF.
ProcThreadSelf ProcBase = 0xFFFF_FFFE_3EAD_5E1F // C.PATHRS_PROC_THREAD_SELF
// ProcBaseTypeMask is __PATHRS_PROC_TYPE_MASK.
ProcBaseTypeMask ProcBase = 0xFFFF_FFFF_0000_0000 // C.__PATHRS_PROC_TYPE_MASK
// ProcBaseTypePid is __PATHRS_PROC_TYPE_PID.
ProcBaseTypePid ProcBase = 0x8000_0000_0000_0000 // C.__PATHRS_PROC_TYPE_PID
// ProcDefaultRootFd is PATHRS_PROC_DEFAULT_ROOTFD.
ProcDefaultRootFd = -int(syscall.EBADF) // C.PATHRS_PROC_DEFAULT_ROOTFD
)
func assertEqual[T comparable](a, b T, msg string) {
if a != b {
panic(fmt.Sprintf("%s ((%T) %#v != (%T) %#v)", msg, a, a, b, b))
}
}
// Verify that the values above match the actual C values. Unfortunately, Go
// only allows us to forcefully cast int64 to uint64 if you use a temporary
// variable, which means we cannot do it in a const context and thus need to do
// it at runtime (even though it is a check that fundamentally could be done at
// compile-time)...
func init() {
var (
actualProcRoot int64 = C.PATHRS_PROC_ROOT
actualProcSelf int64 = C.PATHRS_PROC_SELF
actualProcThreadSelf int64 = C.PATHRS_PROC_THREAD_SELF
)
assertEqual(ProcRoot, ProcBase(actualProcRoot), "PATHRS_PROC_ROOT")
assertEqual(ProcSelf, ProcBase(actualProcSelf), "PATHRS_PROC_SELF")
assertEqual(ProcThreadSelf, ProcBase(actualProcThreadSelf), "PATHRS_PROC_THREAD_SELF")
var (
actualProcBaseTypeMask uint64 = C.__PATHRS_PROC_TYPE_MASK
actualProcBaseTypePid uint64 = C.__PATHRS_PROC_TYPE_PID
)
assertEqual(ProcBaseTypeMask, ProcBase(actualProcBaseTypeMask), "__PATHRS_PROC_TYPE_MASK")
assertEqual(ProcBaseTypePid, ProcBase(actualProcBaseTypePid), "__PATHRS_PROC_TYPE_PID")
assertEqual(ProcDefaultRootFd, int(C.PATHRS_PROC_DEFAULT_ROOTFD), "PATHRS_PROC_DEFAULT_ROOTFD")
}
// ProcPid reimplements the PROC_PID(x) conversion.
func ProcPid(pid uint32) ProcBase { return ProcBaseTypePid | ProcBase(pid) }
// ProcOpenat wraps pathrs_proc_openat.
func ProcOpenat(procRootFd int, base ProcBase, path string, flags int) (uintptr, error) {
cBase := C.pathrs_proc_base_t(base)
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
fd := C.pathrs_proc_openat(C.int(procRootFd), cBase, cPath, C.int(flags))
return uintptr(fd), fetchError(fd)
}
// ProcReadlinkat wraps pathrs_proc_readlinkat.
func ProcReadlinkat(procRootFd int, base ProcBase, path string) (string, error) {
// TODO: See if we can unify this code with InRootReadlink.
cBase := C.pathrs_proc_base_t(base)
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
size := 128
for {
linkBuf := make([]byte, size)
n := C.pathrs_proc_readlinkat(
C.int(procRootFd), cBase, cPath,
C.cast_ptr(unsafe.Pointer(&linkBuf[0])), C.ulong(len(linkBuf)))
switch {
case int(n) < C.__PATHRS_MAX_ERR_VALUE:
return "", fetchError(n)
case int(n) <= len(linkBuf):
return string(linkBuf[:int(n)]), nil
default:
// The contents were truncated. Unlike readlinkat, pathrs returns
// the size of the link when it checked. So use the returned size
// as a basis for the reallocated size (but in order to avoid a DoS
// where a magic-link is growing by a single byte each iteration,
// make sure we are a fair bit larger).
size += int(n)
}
}
}
// ProcfsOpenHow is pathrs_procfs_open_how (struct).
type ProcfsOpenHow C.pathrs_procfs_open_how
const (
// ProcfsNewUnmasked is PATHRS_PROCFS_NEW_UNMASKED.
ProcfsNewUnmasked = C.PATHRS_PROCFS_NEW_UNMASKED
)
// Flags returns a pointer to the internal flags field to allow other packages
// to modify structure fields that are internal due to Go's visibility model.
func (how *ProcfsOpenHow) Flags() *C.uint64_t { return &how.flags }
// ProcfsOpen is pathrs_procfs_open (sizeof(*how) is passed automatically).
func ProcfsOpen(how *ProcfsOpenHow) (uintptr, error) {
fd := C.pathrs_procfs_open((*C.pathrs_procfs_open_how)(how), C.size_t(unsafe.Sizeof(*how)))
return uintptr(fd), fetchError(fd)
}

246
vendor/cyphar.com/go-pathrs/procfs/procfs_linux.go generated vendored Normal file
View File

@@ -0,0 +1,246 @@
//go:build linux
// SPDX-License-Identifier: MPL-2.0
/*
* libpathrs: safe path resolution on Linux
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
* Copyright (C) 2019-2025 SUSE LLC
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
// Package procfs provides a safe API for operating on /proc on Linux.
package procfs
import (
"os"
"runtime"
"cyphar.com/go-pathrs/internal/fdutils"
"cyphar.com/go-pathrs/internal/libpathrs"
)
// ProcBase is used with [ProcReadlink] and related functions to indicate what
// /proc subpath path operations should be done relative to.
type ProcBase struct {
inner libpathrs.ProcBase
}
var (
// ProcRoot indicates to use /proc. Note that this mode may be more
// expensive because we have to take steps to try to avoid leaking unmasked
// procfs handles, so you should use [ProcBaseSelf] if you can.
ProcRoot = ProcBase{inner: libpathrs.ProcRoot}
// ProcSelf indicates to use /proc/self. For most programs, this is the
// standard choice.
ProcSelf = ProcBase{inner: libpathrs.ProcSelf}
// ProcThreadSelf indicates to use /proc/thread-self. In multi-threaded
// programs where one thread has a different CLONE_FS, it is possible for
// /proc/self to point the wrong thread and so /proc/thread-self may be
// necessary.
ProcThreadSelf = ProcBase{inner: libpathrs.ProcThreadSelf}
)
// ProcPid returns a ProcBase which indicates to use /proc/$pid for the given
// PID (or TID). Be aware that due to PID recycling, using this is generally
// not safe except in certain circumstances. Namely:
//
// - PID 1 (the init process), as that PID cannot ever get recycled.
// - Your current PID (though you should just use [ProcBaseSelf]).
// - Your current TID if you have used [runtime.LockOSThread] (though you
// should just use [ProcBaseThreadSelf]).
// - PIDs of child processes (as long as you are sure that no other part of
// your program incorrectly catches or ignores SIGCHLD, and that you do it
// *before* you call wait(2)or any equivalent method that could reap
// zombies).
func ProcPid(pid int) ProcBase {
if pid < 0 || pid >= 1<<31 {
panic("invalid ProcBasePid value") // TODO: should this be an error?
}
return ProcBase{inner: libpathrs.ProcPid(uint32(pid))}
}
// ThreadCloser is a callback that needs to be called when you are done
// operating on an [os.File] fetched using [Handle.OpenThreadSelf].
//
// [os.File]: https://pkg.go.dev/os#File
type ThreadCloser func()
// Handle is a wrapper around an *os.File handle to "/proc", which can be
// used to do further procfs-related operations in a safe way.
type Handle struct {
inner *os.File
}
// Close releases all internal resources for this [Handle].
//
// Note that if the handle is actually the global cached handle, this operation
// is a no-op.
func (proc *Handle) Close() error {
var err error
if proc.inner != nil {
err = proc.inner.Close()
}
return err
}
// OpenOption is a configuration function passed as an argument to [Open].
type OpenOption func(*libpathrs.ProcfsOpenHow) error
// UnmaskedProcRoot can be passed to [Open] to request an unmasked procfs
// handle be created.
//
// procfs, err := procfs.OpenRoot(procfs.UnmaskedProcRoot)
func UnmaskedProcRoot(how *libpathrs.ProcfsOpenHow) error {
*how.Flags() |= libpathrs.ProcfsNewUnmasked
return nil
}
// Open creates a new [Handle] to a safe "/proc", based on the passed
// configuration options (in the form of a series of [OpenOption]s).
func Open(opts ...OpenOption) (*Handle, error) {
var how libpathrs.ProcfsOpenHow
for _, opt := range opts {
if err := opt(&how); err != nil {
return nil, err
}
}
fd, err := libpathrs.ProcfsOpen(&how)
if err != nil {
return nil, err
}
var procFile *os.File
if int(fd) >= 0 {
procFile = os.NewFile(fd, "/proc")
}
// TODO: Check that fd == PATHRS_PROC_DEFAULT_ROOTFD in the <0 case?
return &Handle{inner: procFile}, nil
}
// TODO: Switch to something fdutils.WithFileFd-like.
func (proc *Handle) fd() int {
if proc.inner != nil {
return int(proc.inner.Fd())
}
return libpathrs.ProcDefaultRootFd
}
// TODO: Should we expose open?
func (proc *Handle) open(base ProcBase, path string, flags int) (_ *os.File, Closer ThreadCloser, Err error) {
var closer ThreadCloser
if base == ProcThreadSelf {
runtime.LockOSThread()
closer = runtime.UnlockOSThread
}
defer func() {
if closer != nil && Err != nil {
closer()
Closer = nil
}
}()
fd, err := libpathrs.ProcOpenat(proc.fd(), base.inner, path, flags)
if err != nil {
return nil, nil, err
}
file, err := fdutils.MkFile(fd)
return file, closer, err
}
// OpenRoot safely opens a given path from inside /proc/.
//
// This function must only be used for accessing global information from procfs
// (such as /proc/cpuinfo) or information about other processes (such as
// /proc/1). Accessing your own process information should be done using
// [Handle.OpenSelf] or [Handle.OpenThreadSelf].
func (proc *Handle) OpenRoot(path string, flags int) (*os.File, error) {
file, closer, err := proc.open(ProcRoot, path, flags)
if closer != nil {
// should not happen
panic("non-zero closer returned from procOpen(ProcRoot)")
}
return file, err
}
// OpenSelf safely opens a given path from inside /proc/self/.
//
// This method is recommend for getting process information about the current
// process for almost all Go processes *except* for cases where there are
// [runtime.LockOSThread] threads that have changed some aspect of their state
// (such as through unshare(CLONE_FS) or changing namespaces).
//
// For such non-heterogeneous processes, /proc/self may reference to a task
// that has different state from the current goroutine and so it may be
// preferable to use [Handle.OpenThreadSelf]. The same is true if a user
// really wants to inspect the current OS thread's information (such as
// /proc/thread-self/stack or /proc/thread-self/status which is always uniquely
// per-thread).
//
// Unlike [Handle.OpenThreadSelf], this method does not involve locking
// the goroutine to the current OS thread and so is simpler to use and
// theoretically has slightly less overhead.
//
// [runtime.LockOSThread]: https://pkg.go.dev/runtime#LockOSThread
func (proc *Handle) OpenSelf(path string, flags int) (*os.File, error) {
file, closer, err := proc.open(ProcSelf, path, flags)
if closer != nil {
// should not happen
panic("non-zero closer returned from procOpen(ProcSelf)")
}
return file, err
}
// OpenPid safely opens a given path from inside /proc/$pid/, where pid can be
// either a PID or TID.
//
// This is effectively equivalent to calling [Handle.OpenRoot] with the
// pid prefixed to the subpath.
//
// Be aware that due to PID recycling, using this is generally not safe except
// in certain circumstances. See the documentation of [ProcPid] for more
// details.
func (proc *Handle) OpenPid(pid int, path string, flags int) (*os.File, error) {
file, closer, err := proc.open(ProcPid(pid), path, flags)
if closer != nil {
// should not happen
panic("non-zero closer returned from procOpen(ProcPidOpen)")
}
return file, err
}
// OpenThreadSelf safely opens a given path from inside /proc/thread-self/.
//
// Most Go processes have heterogeneous threads (all threads have most of the
// same kernel state such as CLONE_FS) and so [Handle.OpenSelf] is
// preferable for most users.
//
// For non-heterogeneous threads, or users that actually want thread-specific
// information (such as /proc/thread-self/stack or /proc/thread-self/status),
// this method is necessary.
//
// Because Go can change the running OS thread of your goroutine without notice
// (and then subsequently kill the old thread), this method will lock the
// current goroutine to the OS thread (with [runtime.LockOSThread]) and the
// caller is responsible for unlocking the the OS thread with the
// [ThreadCloser] callback once they are done using the returned file. This
// callback MUST be called AFTER you have finished using the returned
// [os.File]. This callback is completely separate to [os.File.Close], so it
// must be called regardless of how you close the handle.
//
// [runtime.LockOSThread]: https://pkg.go.dev/runtime#LockOSThread
// [os.File]: https://pkg.go.dev/os#File
// [os.File.Close]: https://pkg.go.dev/os#File.Close
func (proc *Handle) OpenThreadSelf(path string, flags int) (*os.File, ThreadCloser, error) {
return proc.open(ProcThreadSelf, path, flags)
}
// Readlink safely reads the contents of a symlink from the given procfs base.
//
// This is effectively equivalent to doing an Open*(O_PATH|O_NOFOLLOW) of the
// path and then doing unix.Readlinkat(fd, ""), but with the benefit that
// thread locking is not necessary for [ProcThreadSelf].
func (proc *Handle) Readlink(base ProcBase, path string) (string, error) {
return libpathrs.ProcReadlinkat(proc.fd(), base.inner, path)
}

367
vendor/cyphar.com/go-pathrs/root_linux.go generated vendored Normal file
View File

@@ -0,0 +1,367 @@
//go:build linux
// SPDX-License-Identifier: MPL-2.0
/*
* libpathrs: safe path resolution on Linux
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
* Copyright (C) 2019-2025 SUSE LLC
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package pathrs
import (
"errors"
"fmt"
"os"
"syscall"
"cyphar.com/go-pathrs/internal/fdutils"
"cyphar.com/go-pathrs/internal/libpathrs"
)
// Root is a handle to the root of a directory tree to resolve within. The only
// purpose of this "root handle" is to perform operations within the directory
// tree, or to get a [Handle] to inodes within the directory tree.
//
// At time of writing, it is considered a *VERY BAD IDEA* to open a [Root]
// inside a possibly-attacker-controlled directory tree. While we do have
// protections that should defend against it, it's far more dangerous than just
// opening a directory tree which is not inside a potentially-untrusted
// directory.
type Root struct {
inner *os.File
}
// OpenRoot creates a new [Root] handle to the directory at the given path.
func OpenRoot(path string) (*Root, error) {
fd, err := libpathrs.OpenRoot(path)
if err != nil {
return nil, err
}
file, err := fdutils.MkFile(fd)
if err != nil {
return nil, err
}
return &Root{inner: file}, nil
}
// RootFromFile creates a new [Root] handle from an [os.File] referencing a
// directory. The provided file will be duplicated, so the original file should
// still be closed by the caller.
//
// This is effectively the inverse operation of [Root.IntoFile].
//
// [os.File]: https://pkg.go.dev/os#File
func RootFromFile(file *os.File) (*Root, error) {
newFile, err := fdutils.DupFile(file)
if err != nil {
return nil, fmt.Errorf("duplicate root fd: %w", err)
}
return &Root{inner: newFile}, nil
}
// Resolve resolves the given path within the [Root]'s directory tree, and
// returns a [Handle] to the resolved path. The path must already exist,
// otherwise an error will occur.
//
// All symlinks (including trailing symlinks) are followed, but they are
// resolved within the rootfs. If you wish to open a handle to the symlink
// itself, use [ResolveNoFollow].
func (r *Root) Resolve(path string) (*Handle, error) {
return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*Handle, error) {
handleFd, err := libpathrs.InRootResolve(rootFd, path)
if err != nil {
return nil, err
}
handleFile, err := fdutils.MkFile(handleFd)
if err != nil {
return nil, err
}
return &Handle{inner: handleFile}, nil
})
}
// ResolveNoFollow is effectively an O_NOFOLLOW version of [Resolve]. Their
// behaviour is identical, except that *trailing* symlinks will not be
// followed. If the final component is a trailing symlink, an O_PATH|O_NOFOLLOW
// handle to the symlink itself is returned.
func (r *Root) ResolveNoFollow(path string) (*Handle, error) {
return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*Handle, error) {
handleFd, err := libpathrs.InRootResolveNoFollow(rootFd, path)
if err != nil {
return nil, err
}
handleFile, err := fdutils.MkFile(handleFd)
if err != nil {
return nil, err
}
return &Handle{inner: handleFile}, nil
})
}
// Open is effectively shorthand for [Resolve] followed by [Handle.Open], but
// can be slightly more efficient (it reduces CGo overhead and the number of
// syscalls used when using the openat2-based resolver) and is arguably more
// ergonomic to use.
//
// This is effectively equivalent to [os.Open].
//
// [os.Open]: https://pkg.go.dev/os#Open
func (r *Root) Open(path string) (*os.File, error) {
return r.OpenFile(path, os.O_RDONLY)
}
// OpenFile is effectively shorthand for [Resolve] followed by
// [Handle.OpenFile], but can be slightly more efficient (it reduces CGo
// overhead and the number of syscalls used when using the openat2-based
// resolver) and is arguably more ergonomic to use.
//
// However, if flags contains os.O_NOFOLLOW and the path is a symlink, then
// OpenFile's behaviour will match that of openat2. In most cases an error will
// be returned, but if os.O_PATH is provided along with os.O_NOFOLLOW then a
// file equivalent to [ResolveNoFollow] will be returned instead.
//
// This is effectively equivalent to [os.OpenFile], except that os.O_CREAT is
// not supported.
//
// [os.OpenFile]: https://pkg.go.dev/os#OpenFile
func (r *Root) OpenFile(path string, flags int) (*os.File, error) {
return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*os.File, error) {
fd, err := libpathrs.InRootOpen(rootFd, path, flags)
if err != nil {
return nil, err
}
return fdutils.MkFile(fd)
})
}
// Create creates a file within the [Root]'s directory tree at the given path,
// and returns a handle to the file. The provided mode is used for the new file
// (the process's umask applies).
//
// Unlike [os.Create], if the file already exists an error is created rather
// than the file being opened and truncated.
//
// [os.Create]: https://pkg.go.dev/os#Create
func (r *Root) Create(path string, flags int, mode os.FileMode) (*os.File, error) {
unixMode, err := toUnixMode(mode, false)
if err != nil {
return nil, err
}
return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*os.File, error) {
handleFd, err := libpathrs.InRootCreat(rootFd, path, flags, unixMode)
if err != nil {
return nil, err
}
return fdutils.MkFile(handleFd)
})
}
// Rename two paths within a [Root]'s directory tree. The flags argument is
// identical to the RENAME_* flags to the renameat2(2) system call.
func (r *Root) Rename(src, dst string, flags uint) error {
_, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
err := libpathrs.InRootRename(rootFd, src, dst, flags)
return struct{}{}, err
})
return err
}
// RemoveDir removes the named empty directory within a [Root]'s directory
// tree.
func (r *Root) RemoveDir(path string) error {
_, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
err := libpathrs.InRootRmdir(rootFd, path)
return struct{}{}, err
})
return err
}
// RemoveFile removes the named file within a [Root]'s directory tree.
func (r *Root) RemoveFile(path string) error {
_, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
err := libpathrs.InRootUnlink(rootFd, path)
return struct{}{}, err
})
return err
}
// Remove removes the named file or (empty) directory within a [Root]'s
// directory tree.
//
// This is effectively equivalent to [os.Remove].
//
// [os.Remove]: https://pkg.go.dev/os#Remove
func (r *Root) Remove(path string) error {
// In order to match os.Remove's implementation we need to also do both
// syscalls unconditionally and adjust the error based on whether
// pathrs_inroot_rmdir() returned ENOTDIR.
unlinkErr := r.RemoveFile(path)
if unlinkErr == nil {
return nil
}
rmdirErr := r.RemoveDir(path)
if rmdirErr == nil {
return nil
}
// Both failed, adjust the error in the same way that os.Remove does.
err := rmdirErr
if errors.Is(err, syscall.ENOTDIR) {
err = unlinkErr
}
return err
}
// RemoveAll recursively deletes a path and all of its children.
//
// This is effectively equivalent to [os.RemoveAll].
//
// [os.RemoveAll]: https://pkg.go.dev/os#RemoveAll
func (r *Root) RemoveAll(path string) error {
_, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
err := libpathrs.InRootRemoveAll(rootFd, path)
return struct{}{}, err
})
return err
}
// Mkdir creates a directory within a [Root]'s directory tree. The provided
// mode is used for the new directory (the process's umask applies).
//
// This is effectively equivalent to [os.Mkdir].
//
// [os.Mkdir]: https://pkg.go.dev/os#Mkdir
func (r *Root) Mkdir(path string, mode os.FileMode) error {
unixMode, err := toUnixMode(mode, false)
if err != nil {
return err
}
_, err = fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
err := libpathrs.InRootMkdir(rootFd, path, unixMode)
return struct{}{}, err
})
return err
}
// MkdirAll creates a directory (and any parent path components if they don't
// exist) within a [Root]'s directory tree. The provided mode is used for any
// directories created by this function (the process's umask applies).
//
// This is effectively equivalent to [os.MkdirAll].
//
// [os.MkdirAll]: https://pkg.go.dev/os#MkdirAll
func (r *Root) MkdirAll(path string, mode os.FileMode) (*Handle, error) {
unixMode, err := toUnixMode(mode, false)
if err != nil {
return nil, err
}
return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (*Handle, error) {
handleFd, err := libpathrs.InRootMkdirAll(rootFd, path, unixMode)
if err != nil {
return nil, err
}
handleFile, err := fdutils.MkFile(handleFd)
if err != nil {
return nil, err
}
return &Handle{inner: handleFile}, err
})
}
// Mknod creates a new device inode of the given type within a [Root]'s
// directory tree. The provided mode is used for the new directory (the
// process's umask applies).
//
// This is effectively equivalent to [unix.Mknod].
//
// [unix.Mknod]: https://pkg.go.dev/golang.org/x/sys/unix#Mknod
func (r *Root) Mknod(path string, mode os.FileMode, dev uint64) error {
unixMode, err := toUnixMode(mode, true)
if err != nil {
return err
}
_, err = fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
err := libpathrs.InRootMknod(rootFd, path, unixMode, dev)
return struct{}{}, err
})
return err
}
// Symlink creates a symlink within a [Root]'s directory tree. The symlink is
// created at path and is a link to target.
//
// This is effectively equivalent to [os.Symlink].
//
// [os.Symlink]: https://pkg.go.dev/os#Symlink
func (r *Root) Symlink(path, target string) error {
_, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
err := libpathrs.InRootSymlink(rootFd, path, target)
return struct{}{}, err
})
return err
}
// Hardlink creates a hardlink within a [Root]'s directory tree. The hardlink
// is created at path and is a link to target. Both paths are within the
// [Root]'s directory tree (you cannot hardlink to a different [Root] or the
// host).
//
// This is effectively equivalent to [os.Link].
//
// [os.Link]: https://pkg.go.dev/os#Link
func (r *Root) Hardlink(path, target string) error {
_, err := fdutils.WithFileFd(r.inner, func(rootFd uintptr) (struct{}, error) {
err := libpathrs.InRootHardlink(rootFd, path, target)
return struct{}{}, err
})
return err
}
// Readlink returns the target of a symlink with a [Root]'s directory tree.
//
// This is effectively equivalent to [os.Readlink].
//
// [os.Readlink]: https://pkg.go.dev/os#Readlink
func (r *Root) Readlink(path string) (string, error) {
return fdutils.WithFileFd(r.inner, func(rootFd uintptr) (string, error) {
return libpathrs.InRootReadlink(rootFd, path)
})
}
// IntoFile unwraps the [Root] into its underlying [os.File].
//
// It is critical that you do not operate on this file descriptor yourself,
// because the security properties of libpathrs depend on users doing all
// relevant filesystem operations through libpathrs.
//
// This operation returns the internal [os.File] of the [Root] directly, so
// calling [Root.Close] will also close any copies of the returned [os.File].
// If you want to get an independent copy, use [Root.Clone] followed by
// [Root.IntoFile] on the cloned [Root].
//
// [os.File]: https://pkg.go.dev/os#File
func (r *Root) IntoFile() *os.File {
// TODO: Figure out if we really don't want to make a copy.
// TODO: We almost certainly want to clear r.inner here, but we can't do
// that easily atomically (we could use atomic.Value but that'll make
// things quite a bit uglier).
return r.inner
}
// Clone creates a copy of a [Root] handle, such that it has a separate
// lifetime to the original (while referring to the same underlying directory).
func (r *Root) Clone() (*Root, error) {
return RootFromFile(r.inner)
}
// Close frees all of the resources used by the [Root] handle.
func (r *Root) Close() error {
return r.inner.Close()
}

56
vendor/cyphar.com/go-pathrs/utils_linux.go generated vendored Normal file
View File

@@ -0,0 +1,56 @@
//go:build linux
// SPDX-License-Identifier: MPL-2.0
/*
* libpathrs: safe path resolution on Linux
* Copyright (C) 2019-2025 Aleksa Sarai <cyphar@cyphar.com>
* Copyright (C) 2019-2025 SUSE LLC
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
package pathrs
import (
"fmt"
"os"
"golang.org/x/sys/unix"
)
//nolint:cyclop // this function needs to handle a lot of cases
func toUnixMode(mode os.FileMode, needsType bool) (uint32, error) {
sysMode := uint32(mode.Perm())
switch mode & os.ModeType { //nolint:exhaustive // we only care about ModeType bits
case 0:
if needsType {
sysMode |= unix.S_IFREG
}
case os.ModeDir:
sysMode |= unix.S_IFDIR
case os.ModeSymlink:
sysMode |= unix.S_IFLNK
case os.ModeCharDevice | os.ModeDevice:
sysMode |= unix.S_IFCHR
case os.ModeDevice:
sysMode |= unix.S_IFBLK
case os.ModeNamedPipe:
sysMode |= unix.S_IFIFO
case os.ModeSocket:
sysMode |= unix.S_IFSOCK
default:
return 0, fmt.Errorf("invalid mode filetype %+o", mode)
}
if mode&os.ModeSetuid != 0 {
sysMode |= unix.S_ISUID
}
if mode&os.ModeSetgid != 0 {
sysMode |= unix.S_ISGID
}
if mode&os.ModeSticky != 0 {
sysMode |= unix.S_ISVTX
}
return sysMode, nil
}

View File

@@ -1,7 +1,7 @@
TOML stands for Tom's Obvious, Minimal Language. This Go package provides a
reflection interface similar to Go's standard library `json` and `xml` packages.
Compatible with TOML version [v1.0.0](https://toml.io/en/v1.0.0).
Compatible with TOML version [v1.1.0](https://toml.io/en/v1.1.0).
Documentation: https://pkg.go.dev/github.com/BurntSushi/toml

View File

@@ -206,6 +206,13 @@ func markDecodedRecursive(md *MetaData, tmap map[string]any) {
markDecodedRecursive(md, tmap)
md.context = md.context[0 : len(md.context)-1]
}
if tarr, ok := tmap[key].([]map[string]any); ok {
for _, elm := range tarr {
md.context = append(md.context, key)
markDecodedRecursive(md, elm)
md.context = md.context[0 : len(md.context)-1]
}
}
}
}
@@ -423,7 +430,7 @@ func (md *MetaData) unifyString(data any, rv reflect.Value) error {
if i, ok := data.(int64); ok {
rv.SetString(strconv.FormatInt(i, 10))
} else if f, ok := data.(float64); ok {
rv.SetString(strconv.FormatFloat(f, 'f', -1, 64))
rv.SetString(strconv.FormatFloat(f, 'g', -1, 64))
} else {
return md.badtype("string", data)
}

View File

@@ -228,9 +228,9 @@ func (enc *Encoder) eElement(rv reflect.Value) {
}
switch v.Location() {
default:
enc.wf(v.Format(format))
enc.write(v.Format(format))
case internal.LocalDatetime, internal.LocalDate, internal.LocalTime:
enc.wf(v.In(time.UTC).Format(format))
enc.write(v.In(time.UTC).Format(format))
}
return
case Marshaler:
@@ -279,40 +279,40 @@ func (enc *Encoder) eElement(rv reflect.Value) {
case reflect.String:
enc.writeQuoted(rv.String())
case reflect.Bool:
enc.wf(strconv.FormatBool(rv.Bool()))
enc.write(strconv.FormatBool(rv.Bool()))
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
enc.wf(strconv.FormatInt(rv.Int(), 10))
enc.write(strconv.FormatInt(rv.Int(), 10))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
enc.wf(strconv.FormatUint(rv.Uint(), 10))
enc.write(strconv.FormatUint(rv.Uint(), 10))
case reflect.Float32:
f := rv.Float()
if math.IsNaN(f) {
if math.Signbit(f) {
enc.wf("-")
enc.write("-")
}
enc.wf("nan")
enc.write("nan")
} else if math.IsInf(f, 0) {
if math.Signbit(f) {
enc.wf("-")
enc.write("-")
}
enc.wf("inf")
enc.write("inf")
} else {
enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 32)))
enc.write(floatAddDecimal(strconv.FormatFloat(f, 'g', -1, 32)))
}
case reflect.Float64:
f := rv.Float()
if math.IsNaN(f) {
if math.Signbit(f) {
enc.wf("-")
enc.write("-")
}
enc.wf("nan")
enc.write("nan")
} else if math.IsInf(f, 0) {
if math.Signbit(f) {
enc.wf("-")
enc.write("-")
}
enc.wf("inf")
enc.write("inf")
} else {
enc.wf(floatAddDecimal(strconv.FormatFloat(f, 'f', -1, 64)))
enc.write(floatAddDecimal(strconv.FormatFloat(f, 'g', -1, 64)))
}
case reflect.Array, reflect.Slice:
enc.eArrayOrSliceElement(rv)
@@ -330,27 +330,32 @@ func (enc *Encoder) eElement(rv reflect.Value) {
// By the TOML spec, all floats must have a decimal with at least one number on
// either side.
func floatAddDecimal(fstr string) string {
if !strings.Contains(fstr, ".") {
return fstr + ".0"
for _, c := range fstr {
if c == 'e' { // Exponent syntax
return fstr
}
if c == '.' {
return fstr
}
}
return fstr
return fstr + ".0"
}
func (enc *Encoder) writeQuoted(s string) {
enc.wf("\"%s\"", dblQuotedReplacer.Replace(s))
enc.write(`"` + dblQuotedReplacer.Replace(s) + `"`)
}
func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) {
length := rv.Len()
enc.wf("[")
enc.write("[")
for i := 0; i < length; i++ {
elem := eindirect(rv.Index(i))
enc.eElement(elem)
if i != length-1 {
enc.wf(", ")
enc.write(", ")
}
}
enc.wf("]")
enc.write("]")
}
func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
@@ -363,7 +368,7 @@ func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
continue
}
enc.newline()
enc.wf("%s[[%s]]", enc.indentStr(key), key)
enc.writef("%s[[%s]]", enc.indentStr(key), key)
enc.newline()
enc.eMapOrStruct(key, trv, false)
}
@@ -376,7 +381,7 @@ func (enc *Encoder) eTable(key Key, rv reflect.Value) {
enc.newline()
}
if len(key) > 0 {
enc.wf("%s[%s]", enc.indentStr(key), key)
enc.writef("%s[%s]", enc.indentStr(key), key)
enc.newline()
}
enc.eMapOrStruct(key, rv, false)
@@ -422,7 +427,7 @@ func (enc *Encoder) eMap(key Key, rv reflect.Value, inline bool) {
if inline {
enc.writeKeyValue(Key{mapKey.String()}, val, true)
if trailC || i != len(mapKeys)-1 {
enc.wf(", ")
enc.write(", ")
}
} else {
enc.encode(key.add(mapKey.String()), val)
@@ -431,12 +436,12 @@ func (enc *Encoder) eMap(key Key, rv reflect.Value, inline bool) {
}
if inline {
enc.wf("{")
enc.write("{")
}
writeMapKeys(mapKeysDirect, len(mapKeysSub) > 0)
writeMapKeys(mapKeysSub, false)
if inline {
enc.wf("}")
enc.write("}")
}
}
@@ -534,7 +539,7 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) {
if inline {
enc.writeKeyValue(Key{keyName}, fieldVal, true)
if fieldIndex[0] != totalFields-1 {
enc.wf(", ")
enc.write(", ")
}
} else {
enc.encode(key.add(keyName), fieldVal)
@@ -543,14 +548,14 @@ func (enc *Encoder) eStruct(key Key, rv reflect.Value, inline bool) {
}
if inline {
enc.wf("{")
enc.write("{")
}
l := len(fieldsDirect) + len(fieldsSub)
writeFields(fieldsDirect, l)
writeFields(fieldsSub, l)
if inline {
enc.wf("}")
enc.write("}")
}
}
@@ -700,7 +705,7 @@ func isEmpty(rv reflect.Value) bool {
func (enc *Encoder) newline() {
if enc.hasWritten {
enc.wf("\n")
enc.write("\n")
}
}
@@ -722,14 +727,22 @@ func (enc *Encoder) writeKeyValue(key Key, val reflect.Value, inline bool) {
enc.eElement(val)
return
}
enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1))
enc.writef("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1))
enc.eElement(val)
if !inline {
enc.newline()
}
}
func (enc *Encoder) wf(format string, v ...any) {
func (enc *Encoder) write(s string) {
_, err := enc.w.WriteString(s)
if err != nil {
encPanic(err)
}
enc.hasWritten = true
}
func (enc *Encoder) writef(format string, v ...any) {
_, err := fmt.Fprintf(enc.w, format, v...)
if err != nil {
encPanic(err)

View File

@@ -13,7 +13,6 @@ type itemType int
const (
itemError itemType = iota
itemNIL // used in the parser to indicate no type
itemEOF
itemText
itemString
@@ -47,14 +46,13 @@ func (p Position) String() string {
}
type lexer struct {
input string
start int
pos int
line int
state stateFn
items chan item
tomlNext bool
esc bool
input string
start int
pos int
line int
state stateFn
items chan item
esc bool
// Allow for backing up up to 4 runes. This is necessary because TOML
// contains 3-rune tokens (""" and ''').
@@ -90,14 +88,13 @@ func (lx *lexer) nextItem() item {
}
}
func lex(input string, tomlNext bool) *lexer {
func lex(input string) *lexer {
lx := &lexer{
input: input,
state: lexTop,
items: make(chan item, 10),
stack: make([]stateFn, 0, 10),
line: 1,
tomlNext: tomlNext,
input: input,
state: lexTop,
items: make(chan item, 10),
stack: make([]stateFn, 0, 10),
line: 1,
}
return lx
}
@@ -108,7 +105,7 @@ func (lx *lexer) push(state stateFn) {
func (lx *lexer) pop() stateFn {
if len(lx.stack) == 0 {
return lx.errorf("BUG in lexer: no states to pop")
panic("BUG in lexer: no states to pop")
}
last := lx.stack[len(lx.stack)-1]
lx.stack = lx.stack[0 : len(lx.stack)-1]
@@ -305,6 +302,8 @@ func lexTop(lx *lexer) stateFn {
return lexTableStart
case eof:
if lx.pos > lx.start {
// TODO: never reached? I think this can only occur on a bug in the
// lexer(?)
return lx.errorf("unexpected EOF")
}
lx.emit(itemEOF)
@@ -392,8 +391,6 @@ func lexTableNameStart(lx *lexer) stateFn {
func lexTableNameEnd(lx *lexer) stateFn {
lx.skip(isWhitespace)
switch r := lx.next(); {
case isWhitespace(r):
return lexTableNameEnd
case r == '.':
lx.ignore()
return lexTableNameStart
@@ -412,7 +409,7 @@ func lexTableNameEnd(lx *lexer) stateFn {
// Lexes only one part, e.g. only 'a' inside 'a.b'.
func lexBareName(lx *lexer) stateFn {
r := lx.next()
if isBareKeyChar(r, lx.tomlNext) {
if isBareKeyChar(r) {
return lexBareName
}
lx.backup()
@@ -420,23 +417,23 @@ func lexBareName(lx *lexer) stateFn {
return lx.pop()
}
// lexBareName lexes one part of a key or table.
//
// It assumes that at least one valid character for the table has already been
// read.
// lexQuotedName lexes one part of a quoted key or table name. It assumes that
// it starts lexing at the quote itself (" or ').
//
// Lexes only one part, e.g. only '"a"' inside '"a".b'.
func lexQuotedName(lx *lexer) stateFn {
r := lx.next()
switch {
case isWhitespace(r):
return lexSkip(lx, lexValue)
case r == '"':
lx.ignore() // ignore the '"'
return lexString
case r == '\'':
lx.ignore() // ignore the "'"
return lexRawString
// TODO: I don't think any of the below conditions can ever be reached?
case isWhitespace(r):
return lexSkip(lx, lexValue)
case r == eof:
return lx.errorf("unexpected EOF; expected value")
default:
@@ -464,17 +461,19 @@ func lexKeyStart(lx *lexer) stateFn {
func lexKeyNameStart(lx *lexer) stateFn {
lx.skip(isWhitespace)
switch r := lx.peek(); {
case r == '=' || r == eof:
return lx.errorf("unexpected '='")
case r == '.':
return lx.errorf("unexpected '.'")
default:
lx.push(lexKeyEnd)
return lexBareName
case r == '"' || r == '\'':
lx.ignore()
lx.push(lexKeyEnd)
return lexQuotedName
default:
lx.push(lexKeyEnd)
return lexBareName
// TODO: I think these can never be reached?
case r == '=' || r == eof:
return lx.errorf("unexpected '='")
case r == '.':
return lx.errorf("unexpected '.'")
}
}
@@ -485,7 +484,7 @@ func lexKeyEnd(lx *lexer) stateFn {
switch r := lx.next(); {
case isWhitespace(r):
return lexSkip(lx, lexKeyEnd)
case r == eof:
case r == eof: // TODO: never reached
return lx.errorf("unexpected EOF; expected key separator '='")
case r == '.':
lx.ignore()
@@ -628,10 +627,7 @@ func lexInlineTableValue(lx *lexer) stateFn {
case isWhitespace(r):
return lexSkip(lx, lexInlineTableValue)
case isNL(r):
if lx.tomlNext {
return lexSkip(lx, lexInlineTableValue)
}
return lx.errorPrevLine(errLexInlineTableNL{})
return lexSkip(lx, lexInlineTableValue)
case r == '#':
lx.push(lexInlineTableValue)
return lexCommentStart
@@ -653,10 +649,7 @@ func lexInlineTableValueEnd(lx *lexer) stateFn {
case isWhitespace(r):
return lexSkip(lx, lexInlineTableValueEnd)
case isNL(r):
if lx.tomlNext {
return lexSkip(lx, lexInlineTableValueEnd)
}
return lx.errorPrevLine(errLexInlineTableNL{})
return lexSkip(lx, lexInlineTableValueEnd)
case r == '#':
lx.push(lexInlineTableValueEnd)
return lexCommentStart
@@ -664,10 +657,7 @@ func lexInlineTableValueEnd(lx *lexer) stateFn {
lx.ignore()
lx.skip(isWhitespace)
if lx.peek() == '}' {
if lx.tomlNext {
return lexInlineTableValueEnd
}
return lx.errorf("trailing comma not allowed in inline tables")
return lexInlineTableValueEnd
}
return lexInlineTableValue
case r == '}':
@@ -855,9 +845,6 @@ func lexStringEscape(lx *lexer) stateFn {
r := lx.next()
switch r {
case 'e':
if !lx.tomlNext {
return lx.error(errLexEscape{r})
}
fallthrough
case 'b':
fallthrough
@@ -878,9 +865,6 @@ func lexStringEscape(lx *lexer) stateFn {
case '\\':
return lx.pop()
case 'x':
if !lx.tomlNext {
return lx.error(errLexEscape{r})
}
return lexHexEscape
case 'u':
return lexShortUnicodeEscape
@@ -928,19 +912,9 @@ func lexLongUnicodeEscape(lx *lexer) stateFn {
// lexBaseNumberOrDate can differentiate base prefixed integers from other
// types.
func lexNumberOrDateStart(lx *lexer) stateFn {
r := lx.next()
switch r {
case '0':
if lx.next() == '0' {
return lexBaseNumberOrDate
}
if !isDigit(r) {
// The only way to reach this state is if the value starts
// with a digit, so specifically treat anything else as an
// error.
return lx.errorf("expected a digit but got %q", r)
}
return lexNumberOrDate
}
@@ -1196,13 +1170,13 @@ func lexSkip(lx *lexer, nextState stateFn) stateFn {
}
func (s stateFn) String() string {
if s == nil {
return "<nil>"
}
name := runtime.FuncForPC(reflect.ValueOf(s).Pointer()).Name()
if i := strings.LastIndexByte(name, '.'); i > -1 {
name = name[i+1:]
}
if s == nil {
name = "<nil>"
}
return name + "()"
}
@@ -1210,8 +1184,6 @@ func (itype itemType) String() string {
switch itype {
case itemError:
return "Error"
case itemNIL:
return "NIL"
case itemEOF:
return "EOF"
case itemText:
@@ -1226,18 +1198,22 @@ func (itype itemType) String() string {
return "Float"
case itemDatetime:
return "DateTime"
case itemTableStart:
return "TableStart"
case itemTableEnd:
return "TableEnd"
case itemKeyStart:
return "KeyStart"
case itemKeyEnd:
return "KeyEnd"
case itemArray:
return "Array"
case itemArrayEnd:
return "ArrayEnd"
case itemTableStart:
return "TableStart"
case itemTableEnd:
return "TableEnd"
case itemArrayTableStart:
return "ArrayTableStart"
case itemArrayTableEnd:
return "ArrayTableEnd"
case itemKeyStart:
return "KeyStart"
case itemKeyEnd:
return "KeyEnd"
case itemCommentStart:
return "CommentStart"
case itemInlineTableStart:
@@ -1266,7 +1242,7 @@ func isDigit(r rune) bool { return r >= '0' && r <= '9' }
func isBinary(r rune) bool { return r == '0' || r == '1' }
func isOctal(r rune) bool { return r >= '0' && r <= '7' }
func isHex(r rune) bool { return (r >= '0' && r <= '9') || (r|0x20 >= 'a' && r|0x20 <= 'f') }
func isBareKeyChar(r rune, tomlNext bool) bool {
func isBareKeyChar(r rune) bool {
return (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') ||
(r >= '0' && r <= '9') || r == '_' || r == '-'
}

View File

@@ -3,7 +3,6 @@ package toml
import (
"fmt"
"math"
"os"
"strconv"
"strings"
"time"
@@ -17,7 +16,6 @@ type parser struct {
context Key // Full key for the current hash in scope.
currentKey string // Base key name for everything except hashes.
pos Position // Current position in the TOML file.
tomlNext bool
ordered []Key // List of keys in the order that they appear in the TOML data.
@@ -32,8 +30,6 @@ type keyInfo struct {
}
func parse(data string) (p *parser, err error) {
_, tomlNext := os.LookupEnv("BURNTSUSHI_TOML_110")
defer func() {
if r := recover(); r != nil {
if pErr, ok := r.(ParseError); ok {
@@ -73,10 +69,9 @@ func parse(data string) (p *parser, err error) {
p = &parser{
keyInfo: make(map[string]keyInfo),
mapping: make(map[string]any),
lx: lex(data, tomlNext),
lx: lex(data),
ordered: make([]Key, 0),
implicits: make(map[string]struct{}),
tomlNext: tomlNext,
}
for {
item := p.next()
@@ -350,17 +345,14 @@ func (p *parser) valueFloat(it item) (any, tomlType) {
var dtTypes = []struct {
fmt string
zone *time.Location
next bool
}{
{time.RFC3339Nano, time.Local, false},
{"2006-01-02T15:04:05.999999999", internal.LocalDatetime, false},
{"2006-01-02", internal.LocalDate, false},
{"15:04:05.999999999", internal.LocalTime, false},
// tomlNext
{"2006-01-02T15:04Z07:00", time.Local, true},
{"2006-01-02T15:04", internal.LocalDatetime, true},
{"15:04", internal.LocalTime, true},
{time.RFC3339Nano, time.Local},
{"2006-01-02T15:04:05.999999999", internal.LocalDatetime},
{"2006-01-02", internal.LocalDate},
{"15:04:05.999999999", internal.LocalTime},
{"2006-01-02T15:04Z07:00", time.Local},
{"2006-01-02T15:04", internal.LocalDatetime},
{"15:04", internal.LocalTime},
}
func (p *parser) valueDatetime(it item) (any, tomlType) {
@@ -371,9 +363,6 @@ func (p *parser) valueDatetime(it item) (any, tomlType) {
err error
)
for _, dt := range dtTypes {
if dt.next && !p.tomlNext {
continue
}
t, err = time.ParseInLocation(dt.fmt, it.val, dt.zone)
if err == nil {
if missingLeadingZero(it.val, dt.fmt) {
@@ -644,6 +633,11 @@ func (p *parser) setValue(key string, value any) {
// Note that since it has already been defined (as a hash), we don't
// want to overwrite it. So our business is done.
if p.isArray(keyContext) {
if !p.isImplicit(keyContext) {
if _, ok := hash[key]; ok {
p.panicf("Key '%s' has already been defined.", keyContext)
}
}
p.removeImplicit(keyContext)
hash[key] = value
return
@@ -802,10 +796,8 @@ func (p *parser) replaceEscapes(it item, str string) string {
b.WriteByte(0x0d)
skip = 1
case 'e':
if p.tomlNext {
b.WriteByte(0x1b)
skip = 1
}
b.WriteByte(0x1b)
skip = 1
case '"':
b.WriteByte(0x22)
skip = 1
@@ -815,11 +807,9 @@ func (p *parser) replaceEscapes(it item, str string) string {
// The lexer guarantees the correct number of characters are present;
// don't need to check here.
case 'x':
if p.tomlNext {
escaped := p.asciiEscapeToUnicode(it, str[i+2:i+4])
b.WriteRune(escaped)
skip = 3
}
escaped := p.asciiEscapeToUnicode(it, str[i+2:i+4])
b.WriteRune(escaped)
skip = 3
case 'u':
escaped := p.asciiEscapeToUnicode(it, str[i+2:i+6])
b.WriteRune(escaped)

View File

@@ -1,3 +0,0 @@
// This file only exists to allow go get on non-Windows platforms.
package backuptar

View File

@@ -1,70 +0,0 @@
//go:build windows
package backuptar
import (
"archive/tar"
"fmt"
"strconv"
"strings"
"time"
)
// Functions copied from https://github.com/golang/go/blob/master/src/archive/tar/strconv.go
// as we need to manage the LIBARCHIVE.creationtime PAXRecord manually.
// Idea taken from containerd which did the same thing.
// parsePAXTime takes a string of the form %d.%d as described in the PAX
// specification. Note that this implementation allows for negative timestamps,
// which is allowed for by the PAX specification, but not always portable.
func parsePAXTime(s string) (time.Time, error) {
const maxNanoSecondDigits = 9
// Split string into seconds and sub-seconds parts.
ss, sn := s, ""
if pos := strings.IndexByte(s, '.'); pos >= 0 {
ss, sn = s[:pos], s[pos+1:]
}
// Parse the seconds.
secs, err := strconv.ParseInt(ss, 10, 64)
if err != nil {
return time.Time{}, tar.ErrHeader
}
if len(sn) == 0 {
return time.Unix(secs, 0), nil // No sub-second values
}
// Parse the nanoseconds.
if strings.Trim(sn, "0123456789") != "" {
return time.Time{}, tar.ErrHeader
}
if len(sn) < maxNanoSecondDigits {
sn += strings.Repeat("0", maxNanoSecondDigits-len(sn)) // Right pad
} else {
sn = sn[:maxNanoSecondDigits] // Right truncate
}
nsecs, _ := strconv.ParseInt(sn, 10, 64) // Must succeed
if len(ss) > 0 && ss[0] == '-' {
return time.Unix(secs, -1*nsecs), nil // Negative correction
}
return time.Unix(secs, nsecs), nil
}
// formatPAXTime converts ts into a time of the form %d.%d as described in the
// PAX specification. This function is capable of negative timestamps.
func formatPAXTime(ts time.Time) (s string) {
secs, nsecs := ts.Unix(), ts.Nanosecond()
if nsecs == 0 {
return strconv.FormatInt(secs, 10)
}
// If seconds is negative, then perform correction.
sign := ""
if secs < 0 {
sign = "-" // Remember sign
secs = -(secs + 1) // Add a second to secs
nsecs = -(nsecs - 1e9) // Take that second away from nsecs
}
return strings.TrimRight(fmt.Sprintf("%s%d.%09d", sign, secs, nsecs), "0")
}

View File

@@ -1,508 +0,0 @@
//go:build windows
// +build windows
package backuptar
import (
"archive/tar"
"encoding/base64"
"fmt"
"io"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/Microsoft/go-winio"
"golang.org/x/sys/windows"
)
//nolint:deadcode,varcheck // keep unused constants for potential future use
const (
cISUID = 0004000 // Set uid
cISGID = 0002000 // Set gid
cISVTX = 0001000 // Save text (sticky bit)
cISDIR = 0040000 // Directory
cISFIFO = 0010000 // FIFO
cISREG = 0100000 // Regular file
cISLNK = 0120000 // Symbolic link
cISBLK = 0060000 // Block special file
cISCHR = 0020000 // Character special file
cISSOCK = 0140000 // Socket
)
const (
hdrFileAttributes = "MSWINDOWS.fileattr"
hdrSecurityDescriptor = "MSWINDOWS.sd"
hdrRawSecurityDescriptor = "MSWINDOWS.rawsd"
hdrMountPoint = "MSWINDOWS.mountpoint"
hdrEaPrefix = "MSWINDOWS.xattr."
hdrCreationTime = "LIBARCHIVE.creationtime"
)
// zeroReader is an io.Reader that always returns 0s.
type zeroReader struct{}
func (zeroReader) Read(b []byte) (int, error) {
for i := range b {
b[i] = 0
}
return len(b), nil
}
func copySparse(t *tar.Writer, br *winio.BackupStreamReader) error {
curOffset := int64(0)
for {
bhdr, err := br.Next()
if err == io.EOF { //nolint:errorlint
err = io.ErrUnexpectedEOF
}
if err != nil {
return err
}
if bhdr.Id != winio.BackupSparseBlock {
return fmt.Errorf("unexpected stream %d", bhdr.Id)
}
// We can't seek backwards, since we have already written that data to the tar.Writer.
if bhdr.Offset < curOffset {
return fmt.Errorf("cannot seek back from %d to %d", curOffset, bhdr.Offset)
}
// archive/tar does not support writing sparse files
// so just write zeroes to catch up to the current offset.
if _, err = io.CopyN(t, zeroReader{}, bhdr.Offset-curOffset); err != nil {
return fmt.Errorf("seek to offset %d: %w", bhdr.Offset, err)
}
if bhdr.Size == 0 {
// A sparse block with size = 0 is used to mark the end of the sparse blocks.
break
}
n, err := io.Copy(t, br)
if err != nil {
return err
}
if n != bhdr.Size {
return fmt.Errorf("copied %d bytes instead of %d at offset %d", n, bhdr.Size, bhdr.Offset)
}
curOffset = bhdr.Offset + n
}
return nil
}
// BasicInfoHeader creates a tar header from basic file information.
func BasicInfoHeader(name string, size int64, fileInfo *winio.FileBasicInfo) *tar.Header {
hdr := &tar.Header{
Format: tar.FormatPAX,
Name: filepath.ToSlash(name),
Size: size,
Typeflag: tar.TypeReg,
ModTime: time.Unix(0, fileInfo.LastWriteTime.Nanoseconds()),
ChangeTime: time.Unix(0, fileInfo.ChangeTime.Nanoseconds()),
AccessTime: time.Unix(0, fileInfo.LastAccessTime.Nanoseconds()),
PAXRecords: make(map[string]string),
}
hdr.PAXRecords[hdrFileAttributes] = fmt.Sprintf("%d", fileInfo.FileAttributes)
hdr.PAXRecords[hdrCreationTime] = formatPAXTime(time.Unix(0, fileInfo.CreationTime.Nanoseconds()))
if (fileInfo.FileAttributes & windows.FILE_ATTRIBUTE_DIRECTORY) != 0 {
hdr.Mode |= cISDIR
hdr.Size = 0
hdr.Typeflag = tar.TypeDir
}
return hdr
}
// SecurityDescriptorFromTarHeader reads the SDDL associated with the header of the current file
// from the tar header and returns the security descriptor into a byte slice.
func SecurityDescriptorFromTarHeader(hdr *tar.Header) ([]byte, error) {
if sdraw, ok := hdr.PAXRecords[hdrRawSecurityDescriptor]; ok {
sd, err := base64.StdEncoding.DecodeString(sdraw)
if err != nil {
// Not returning sd as-is in the error-case, as base64.DecodeString
// may return partially decoded data (not nil or empty slice) in case
// of a failure: https://github.com/golang/go/blob/go1.17.7/src/encoding/base64/base64.go#L382-L387
return nil, err
}
return sd, nil
}
// Maintaining old SDDL-based behavior for backward compatibility. All new
// tar headers written by this library will have raw binary for the security
// descriptor.
if sddl, ok := hdr.PAXRecords[hdrSecurityDescriptor]; ok {
return winio.SddlToSecurityDescriptor(sddl)
}
return nil, nil
}
// ExtendedAttributesFromTarHeader reads the EAs associated with the header of the
// current file from the tar header and returns it as a byte slice.
func ExtendedAttributesFromTarHeader(hdr *tar.Header) ([]byte, error) {
var eas []winio.ExtendedAttribute //nolint:prealloc // len(eas) <= len(hdr.PAXRecords); prealloc is wasteful
for k, v := range hdr.PAXRecords {
if !strings.HasPrefix(k, hdrEaPrefix) {
continue
}
data, err := base64.StdEncoding.DecodeString(v)
if err != nil {
return nil, err
}
eas = append(eas, winio.ExtendedAttribute{
Name: k[len(hdrEaPrefix):],
Value: data,
})
}
var eaData []byte
var err error
if len(eas) != 0 {
eaData, err = winio.EncodeExtendedAttributes(eas)
if err != nil {
return nil, err
}
}
return eaData, nil
}
// EncodeReparsePointFromTarHeader reads the ReparsePoint structure from the tar header
// and encodes it into a byte slice. The file for which this function is called must be a
// symlink.
func EncodeReparsePointFromTarHeader(hdr *tar.Header) []byte {
_, isMountPoint := hdr.PAXRecords[hdrMountPoint]
rp := winio.ReparsePoint{
Target: filepath.FromSlash(hdr.Linkname),
IsMountPoint: isMountPoint,
}
return winio.EncodeReparsePoint(&rp)
}
// WriteTarFileFromBackupStream writes a file to a tar writer using data from a Win32 backup stream.
//
// This encodes Win32 metadata as tar pax vendor extensions starting with MSWINDOWS.
//
// The additional Win32 metadata is:
//
// - MSWINDOWS.fileattr: The Win32 file attributes, as a decimal value
// - MSWINDOWS.rawsd: The Win32 security descriptor, in raw binary format
// - MSWINDOWS.mountpoint: If present, this is a mount point and not a symlink, even though the type is '2' (symlink)
func WriteTarFileFromBackupStream(t *tar.Writer, r io.Reader, name string, size int64, fileInfo *winio.FileBasicInfo) error {
name = filepath.ToSlash(name)
hdr := BasicInfoHeader(name, size, fileInfo)
// If r can be seeked, then this function is two-pass: pass 1 collects the
// tar header data, and pass 2 copies the data stream. If r cannot be
// seeked, then some header data (in particular EAs) will be silently lost.
var (
restartPos int64
err error
)
sr, readTwice := r.(io.Seeker)
if readTwice {
if restartPos, err = sr.Seek(0, io.SeekCurrent); err != nil {
readTwice = false
}
}
br := winio.NewBackupStreamReader(r)
var dataHdr *winio.BackupHeader
for dataHdr == nil {
bhdr, err := br.Next()
if err == io.EOF { //nolint:errorlint
break
}
if err != nil {
return err
}
switch bhdr.Id {
case winio.BackupData:
hdr.Mode |= cISREG
if !readTwice {
dataHdr = bhdr
}
case winio.BackupSecurity:
sd, err := io.ReadAll(br)
if err != nil {
return err
}
hdr.PAXRecords[hdrRawSecurityDescriptor] = base64.StdEncoding.EncodeToString(sd)
case winio.BackupReparseData:
hdr.Mode |= cISLNK
hdr.Typeflag = tar.TypeSymlink
reparseBuffer, _ := io.ReadAll(br)
rp, err := winio.DecodeReparsePoint(reparseBuffer)
if err != nil {
return err
}
if rp.IsMountPoint {
hdr.PAXRecords[hdrMountPoint] = "1"
}
hdr.Linkname = rp.Target
case winio.BackupEaData:
eab, err := io.ReadAll(br)
if err != nil {
return err
}
eas, err := winio.DecodeExtendedAttributes(eab)
if err != nil {
return err
}
for _, ea := range eas {
// Use base64 encoding for the binary value. Note that there
// is no way to encode the EA's flags, since their use doesn't
// make any sense for persisted EAs.
hdr.PAXRecords[hdrEaPrefix+ea.Name] = base64.StdEncoding.EncodeToString(ea.Value)
}
case winio.BackupAlternateData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData:
// ignore these streams
default:
return fmt.Errorf("%s: unknown stream ID %d", name, bhdr.Id)
}
}
err = t.WriteHeader(hdr)
if err != nil {
return err
}
if readTwice {
// Get back to the data stream.
if _, err = sr.Seek(restartPos, io.SeekStart); err != nil {
return err
}
for dataHdr == nil {
bhdr, err := br.Next()
if err == io.EOF { //nolint:errorlint
break
}
if err != nil {
return err
}
if bhdr.Id == winio.BackupData {
dataHdr = bhdr
}
}
}
// The logic for copying file contents is fairly complicated due to the need for handling sparse files,
// and the weird ways they are represented by BackupRead. A normal file will always either have a data stream
// with size and content, or no data stream at all (if empty). However, for a sparse file, the content can also
// be represented using a series of sparse block streams following the data stream. Additionally, the way sparse
// files are handled by BackupRead has changed in the OS recently. The specifics of the representation are described
// in the list at the bottom of this block comment.
//
// Sparse files can be represented in four different ways, based on the specifics of the file.
// - Size = 0:
// Previously: BackupRead yields no data stream and no sparse block streams.
// Recently: BackupRead yields a data stream with size = 0. There are no following sparse block streams.
// - Size > 0, no allocated ranges:
// BackupRead yields a data stream with size = 0. Following is a single sparse block stream with
// size = 0 and offset = <file size>.
// - Size > 0, one allocated range:
// BackupRead yields a data stream with size = <file size> containing the file contents. There are no
// sparse block streams. This is the case if you take a normal file with contents and simply set the
// sparse flag on it.
// - Size > 0, multiple allocated ranges:
// BackupRead yields a data stream with size = 0. Following are sparse block streams for each allocated
// range of the file containing the range contents. Finally there is a sparse block stream with
// size = 0 and offset = <file size>.
if dataHdr != nil { //nolint:nestif // todo: reduce nesting complexity
// A data stream was found. Copy the data.
// We assume that we will either have a data stream size > 0 XOR have sparse block streams.
if dataHdr.Size > 0 || (dataHdr.Attributes&winio.StreamSparseAttributes) == 0 {
if size != dataHdr.Size {
return fmt.Errorf("%s: mismatch between file size %d and header size %d", name, size, dataHdr.Size)
}
if _, err = io.Copy(t, br); err != nil {
return fmt.Errorf("%s: copying contents from data stream: %w", name, err)
}
} else if size > 0 {
// As of a recent OS change, BackupRead now returns a data stream for empty sparse files.
// These files have no sparse block streams, so skip the copySparse call if file size = 0.
if err = copySparse(t, br); err != nil {
return fmt.Errorf("%s: copying contents from sparse block stream: %w", name, err)
}
}
}
// Look for streams after the data stream. The only ones we handle are alternate data streams.
// Other streams may have metadata that could be serialized, but the tar header has already
// been written. In practice, this means that we don't get EA or TXF metadata.
for {
bhdr, err := br.Next()
if err == io.EOF { //nolint:errorlint
break
}
if err != nil {
return err
}
switch bhdr.Id {
case winio.BackupAlternateData:
if (bhdr.Attributes & winio.StreamSparseAttributes) != 0 {
// Unsupported for now, since the size of the alternate stream is not present
// in the backup stream until after the data has been read.
return fmt.Errorf("%s: tar of sparse alternate data streams is unsupported", name)
}
altName := strings.TrimSuffix(bhdr.Name, ":$DATA")
hdr = &tar.Header{
Format: hdr.Format,
Name: name + altName,
Mode: hdr.Mode,
Typeflag: tar.TypeReg,
Size: bhdr.Size,
ModTime: hdr.ModTime,
AccessTime: hdr.AccessTime,
ChangeTime: hdr.ChangeTime,
}
err = t.WriteHeader(hdr)
if err != nil {
return err
}
_, err = io.Copy(t, br)
if err != nil {
return err
}
case winio.BackupEaData, winio.BackupLink, winio.BackupPropertyData, winio.BackupObjectId, winio.BackupTxfsData:
// ignore these streams
default:
return fmt.Errorf("%s: unknown stream ID %d after data", name, bhdr.Id)
}
}
return nil
}
// FileInfoFromHeader retrieves basic Win32 file information from a tar header, using the additional metadata written by
// WriteTarFileFromBackupStream.
func FileInfoFromHeader(hdr *tar.Header) (name string, size int64, fileInfo *winio.FileBasicInfo, err error) {
name = hdr.Name
if hdr.Typeflag == tar.TypeReg {
size = hdr.Size
}
fileInfo = &winio.FileBasicInfo{
LastAccessTime: windows.NsecToFiletime(hdr.AccessTime.UnixNano()),
LastWriteTime: windows.NsecToFiletime(hdr.ModTime.UnixNano()),
ChangeTime: windows.NsecToFiletime(hdr.ChangeTime.UnixNano()),
// Default to ModTime, we'll pull hdrCreationTime below if present
CreationTime: windows.NsecToFiletime(hdr.ModTime.UnixNano()),
}
if attrStr, ok := hdr.PAXRecords[hdrFileAttributes]; ok {
attr, err := strconv.ParseUint(attrStr, 10, 32)
if err != nil {
return "", 0, nil, err
}
fileInfo.FileAttributes = uint32(attr)
} else {
if hdr.Typeflag == tar.TypeDir {
fileInfo.FileAttributes |= windows.FILE_ATTRIBUTE_DIRECTORY
}
}
if creationTimeStr, ok := hdr.PAXRecords[hdrCreationTime]; ok {
creationTime, err := parsePAXTime(creationTimeStr)
if err != nil {
return "", 0, nil, err
}
fileInfo.CreationTime = windows.NsecToFiletime(creationTime.UnixNano())
}
return name, size, fileInfo, err
}
// WriteBackupStreamFromTarFile writes a Win32 backup stream from the current tar file. Since this function may process multiple
// tar file entries in order to collect all the alternate data streams for the file, it returns the next
// tar file that was not processed, or io.EOF is there are no more.
func WriteBackupStreamFromTarFile(w io.Writer, t *tar.Reader, hdr *tar.Header) (*tar.Header, error) {
bw := winio.NewBackupStreamWriter(w)
sd, err := SecurityDescriptorFromTarHeader(hdr)
if err != nil {
return nil, err
}
if len(sd) != 0 {
bhdr := winio.BackupHeader{
Id: winio.BackupSecurity,
Size: int64(len(sd)),
}
err := bw.WriteHeader(&bhdr)
if err != nil {
return nil, err
}
_, err = bw.Write(sd)
if err != nil {
return nil, err
}
}
eadata, err := ExtendedAttributesFromTarHeader(hdr)
if err != nil {
return nil, err
}
if len(eadata) != 0 {
bhdr := winio.BackupHeader{
Id: winio.BackupEaData,
Size: int64(len(eadata)),
}
err = bw.WriteHeader(&bhdr)
if err != nil {
return nil, err
}
_, err = bw.Write(eadata)
if err != nil {
return nil, err
}
}
if hdr.Typeflag == tar.TypeSymlink {
reparse := EncodeReparsePointFromTarHeader(hdr)
bhdr := winio.BackupHeader{
Id: winio.BackupReparseData,
Size: int64(len(reparse)),
}
err := bw.WriteHeader(&bhdr)
if err != nil {
return nil, err
}
_, err = bw.Write(reparse)
if err != nil {
return nil, err
}
}
if hdr.Typeflag == tar.TypeReg {
bhdr := winio.BackupHeader{
Id: winio.BackupData,
Size: hdr.Size,
}
err := bw.WriteHeader(&bhdr)
if err != nil {
return nil, err
}
_, err = io.Copy(bw, t)
if err != nil {
return nil, err
}
}
// Copy all the alternate data streams and return the next non-ADS header.
for {
ahdr, err := t.Next()
if err != nil {
return nil, err
}
if ahdr.Typeflag != tar.TypeReg || !strings.HasPrefix(ahdr.Name, hdr.Name+":") {
return ahdr, nil
}
bhdr := winio.BackupHeader{
Id: winio.BackupAlternateData,
Size: ahdr.Size,
Name: ahdr.Name[len(hdr.Name):] + ":$DATA",
}
err = bw.WriteHeader(&bhdr)
if err != nil {
return nil, err
}
_, err = io.Copy(bw, t)
if err != nil {
return nil, err
}
}
}

View File

@@ -1,377 +0,0 @@
//go:build windows
// +build windows
package vhd
import (
"fmt"
"syscall"
"github.com/Microsoft/go-winio/pkg/guid"
"golang.org/x/sys/windows"
)
//go:generate go run github.com/Microsoft/go-winio/tools/mkwinsyscall -output zvhd_windows.go vhd.go
//sys createVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (win32err error) = virtdisk.CreateVirtualDisk
//sys openVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *openVirtualDiskParameters, handle *syscall.Handle) (win32err error) = virtdisk.OpenVirtualDisk
//sys attachVirtualDisk(handle syscall.Handle, securityDescriptor *uintptr, attachVirtualDiskFlag uint32, providerSpecificFlags uint32, parameters *AttachVirtualDiskParameters, overlapped *syscall.Overlapped) (win32err error) = virtdisk.AttachVirtualDisk
//sys detachVirtualDisk(handle syscall.Handle, detachVirtualDiskFlags uint32, providerSpecificFlags uint32) (win32err error) = virtdisk.DetachVirtualDisk
//sys getVirtualDiskPhysicalPath(handle syscall.Handle, diskPathSizeInBytes *uint32, buffer *uint16) (win32err error) = virtdisk.GetVirtualDiskPhysicalPath
type (
CreateVirtualDiskFlag uint32
VirtualDiskFlag uint32
AttachVirtualDiskFlag uint32
DetachVirtualDiskFlag uint32
VirtualDiskAccessMask uint32
)
type VirtualStorageType struct {
DeviceID uint32
VendorID guid.GUID
}
type CreateVersion2 struct {
UniqueID guid.GUID
MaximumSize uint64
BlockSizeInBytes uint32
SectorSizeInBytes uint32
PhysicalSectorSizeInByte uint32
ParentPath *uint16 // string
SourcePath *uint16 // string
OpenFlags uint32
ParentVirtualStorageType VirtualStorageType
SourceVirtualStorageType VirtualStorageType
ResiliencyGUID guid.GUID
}
type CreateVirtualDiskParameters struct {
Version uint32 // Must always be set to 2
Version2 CreateVersion2
}
type OpenVersion2 struct {
GetInfoOnly bool
ReadOnly bool
ResiliencyGUID guid.GUID
}
type OpenVirtualDiskParameters struct {
Version uint32 // Must always be set to 2
Version2 OpenVersion2
}
// The higher level `OpenVersion2` struct uses `bool`s to refer to `GetInfoOnly` and `ReadOnly` for ease of use. However,
// the internal windows structure uses `BOOL`s aka int32s for these types. `openVersion2` is used for translating
// `OpenVersion2` fields to the correct windows internal field types on the `Open____` methods.
type openVersion2 struct {
getInfoOnly int32
readOnly int32
resiliencyGUID guid.GUID
}
type openVirtualDiskParameters struct {
version uint32
version2 openVersion2
}
type AttachVersion2 struct {
RestrictedOffset uint64
RestrictedLength uint64
}
type AttachVirtualDiskParameters struct {
Version uint32
Version2 AttachVersion2
}
const (
//revive:disable-next-line:var-naming ALL_CAPS
VIRTUAL_STORAGE_TYPE_DEVICE_VHDX = 0x3
// Access Mask for opening a VHD.
VirtualDiskAccessNone VirtualDiskAccessMask = 0x00000000
VirtualDiskAccessAttachRO VirtualDiskAccessMask = 0x00010000
VirtualDiskAccessAttachRW VirtualDiskAccessMask = 0x00020000
VirtualDiskAccessDetach VirtualDiskAccessMask = 0x00040000
VirtualDiskAccessGetInfo VirtualDiskAccessMask = 0x00080000
VirtualDiskAccessCreate VirtualDiskAccessMask = 0x00100000
VirtualDiskAccessMetaOps VirtualDiskAccessMask = 0x00200000
VirtualDiskAccessRead VirtualDiskAccessMask = 0x000d0000
VirtualDiskAccessAll VirtualDiskAccessMask = 0x003f0000
VirtualDiskAccessWritable VirtualDiskAccessMask = 0x00320000
// Flags for creating a VHD.
CreateVirtualDiskFlagNone CreateVirtualDiskFlag = 0x0
CreateVirtualDiskFlagFullPhysicalAllocation CreateVirtualDiskFlag = 0x1
CreateVirtualDiskFlagPreventWritesToSourceDisk CreateVirtualDiskFlag = 0x2
CreateVirtualDiskFlagDoNotCopyMetadataFromParent CreateVirtualDiskFlag = 0x4
CreateVirtualDiskFlagCreateBackingStorage CreateVirtualDiskFlag = 0x8
CreateVirtualDiskFlagUseChangeTrackingSourceLimit CreateVirtualDiskFlag = 0x10
CreateVirtualDiskFlagPreserveParentChangeTrackingState CreateVirtualDiskFlag = 0x20
CreateVirtualDiskFlagVhdSetUseOriginalBackingStorage CreateVirtualDiskFlag = 0x40 //revive:disable-line:var-naming VHD, not Vhd
CreateVirtualDiskFlagSparseFile CreateVirtualDiskFlag = 0x80
CreateVirtualDiskFlagPmemCompatible CreateVirtualDiskFlag = 0x100 //revive:disable-line:var-naming PMEM, not Pmem
CreateVirtualDiskFlagSupportCompressedVolumes CreateVirtualDiskFlag = 0x200
// Flags for opening a VHD.
OpenVirtualDiskFlagNone VirtualDiskFlag = 0x00000000
OpenVirtualDiskFlagNoParents VirtualDiskFlag = 0x00000001
OpenVirtualDiskFlagBlankFile VirtualDiskFlag = 0x00000002
OpenVirtualDiskFlagBootDrive VirtualDiskFlag = 0x00000004
OpenVirtualDiskFlagCachedIO VirtualDiskFlag = 0x00000008
OpenVirtualDiskFlagCustomDiffChain VirtualDiskFlag = 0x00000010
OpenVirtualDiskFlagParentCachedIO VirtualDiskFlag = 0x00000020
OpenVirtualDiskFlagVhdsetFileOnly VirtualDiskFlag = 0x00000040
OpenVirtualDiskFlagIgnoreRelativeParentLocator VirtualDiskFlag = 0x00000080
OpenVirtualDiskFlagNoWriteHardening VirtualDiskFlag = 0x00000100
OpenVirtualDiskFlagSupportCompressedVolumes VirtualDiskFlag = 0x00000200
// Flags for attaching a VHD.
AttachVirtualDiskFlagNone AttachVirtualDiskFlag = 0x00000000
AttachVirtualDiskFlagReadOnly AttachVirtualDiskFlag = 0x00000001
AttachVirtualDiskFlagNoDriveLetter AttachVirtualDiskFlag = 0x00000002
AttachVirtualDiskFlagPermanentLifetime AttachVirtualDiskFlag = 0x00000004
AttachVirtualDiskFlagNoLocalHost AttachVirtualDiskFlag = 0x00000008
AttachVirtualDiskFlagNoSecurityDescriptor AttachVirtualDiskFlag = 0x00000010
AttachVirtualDiskFlagBypassDefaultEncryptionPolicy AttachVirtualDiskFlag = 0x00000020
AttachVirtualDiskFlagNonPnp AttachVirtualDiskFlag = 0x00000040
AttachVirtualDiskFlagRestrictedRange AttachVirtualDiskFlag = 0x00000080
AttachVirtualDiskFlagSinglePartition AttachVirtualDiskFlag = 0x00000100
AttachVirtualDiskFlagRegisterVolume AttachVirtualDiskFlag = 0x00000200
// Flags for detaching a VHD.
DetachVirtualDiskFlagNone DetachVirtualDiskFlag = 0x0
)
// CreateVhdx is a helper function to create a simple vhdx file at the given path using
// default values.
//
//revive:disable-next-line:var-naming VHDX, not Vhdx
func CreateVhdx(path string, maxSizeInGb, blockSizeInMb uint32) error {
params := CreateVirtualDiskParameters{
Version: 2,
Version2: CreateVersion2{
MaximumSize: uint64(maxSizeInGb) * 1024 * 1024 * 1024,
BlockSizeInBytes: blockSizeInMb * 1024 * 1024,
},
}
handle, err := CreateVirtualDisk(path, VirtualDiskAccessNone, CreateVirtualDiskFlagNone, &params)
if err != nil {
return err
}
return syscall.CloseHandle(handle)
}
// DetachVirtualDisk detaches a virtual hard disk by handle.
func DetachVirtualDisk(handle syscall.Handle) (err error) {
if err := detachVirtualDisk(handle, 0, 0); err != nil {
return fmt.Errorf("failed to detach virtual disk: %w", err)
}
return nil
}
// DetachVhd detaches a vhd found at `path`.
//
//revive:disable-next-line:var-naming VHD, not Vhd
func DetachVhd(path string) error {
handle, err := OpenVirtualDisk(
path,
VirtualDiskAccessNone,
OpenVirtualDiskFlagCachedIO|OpenVirtualDiskFlagIgnoreRelativeParentLocator,
)
if err != nil {
return err
}
defer syscall.CloseHandle(handle) //nolint:errcheck
return DetachVirtualDisk(handle)
}
// AttachVirtualDisk attaches a virtual hard disk for use.
func AttachVirtualDisk(
handle syscall.Handle,
attachVirtualDiskFlag AttachVirtualDiskFlag,
parameters *AttachVirtualDiskParameters,
) (err error) {
// Supports both version 1 and 2 of the attach parameters as version 2 wasn't present in RS5.
if err := attachVirtualDisk(
handle,
nil,
uint32(attachVirtualDiskFlag),
0,
parameters,
nil,
); err != nil {
return fmt.Errorf("failed to attach virtual disk: %w", err)
}
return nil
}
// AttachVhd attaches a virtual hard disk at `path` for use. Attaches using version 2
// of the ATTACH_VIRTUAL_DISK_PARAMETERS.
//
//revive:disable-next-line:var-naming VHD, not Vhd
func AttachVhd(path string) (err error) {
handle, err := OpenVirtualDisk(
path,
VirtualDiskAccessNone,
OpenVirtualDiskFlagCachedIO|OpenVirtualDiskFlagIgnoreRelativeParentLocator,
)
if err != nil {
return err
}
defer syscall.CloseHandle(handle) //nolint:errcheck
params := AttachVirtualDiskParameters{Version: 2}
if err := AttachVirtualDisk(
handle,
AttachVirtualDiskFlagNone,
&params,
); err != nil {
return fmt.Errorf("failed to attach virtual disk: %w", err)
}
return nil
}
// OpenVirtualDisk obtains a handle to a VHD opened with supplied access mask and flags.
func OpenVirtualDisk(
vhdPath string,
virtualDiskAccessMask VirtualDiskAccessMask,
openVirtualDiskFlags VirtualDiskFlag,
) (syscall.Handle, error) {
parameters := OpenVirtualDiskParameters{Version: 2}
handle, err := OpenVirtualDiskWithParameters(
vhdPath,
virtualDiskAccessMask,
openVirtualDiskFlags,
&parameters,
)
if err != nil {
return 0, err
}
return handle, nil
}
// OpenVirtualDiskWithParameters obtains a handle to a VHD opened with supplied access mask, flags and parameters.
func OpenVirtualDiskWithParameters(
vhdPath string,
virtualDiskAccessMask VirtualDiskAccessMask,
openVirtualDiskFlags VirtualDiskFlag,
parameters *OpenVirtualDiskParameters,
) (syscall.Handle, error) {
var (
handle syscall.Handle
defaultType VirtualStorageType
getInfoOnly int32
readOnly int32
)
if parameters.Version != 2 {
return handle, fmt.Errorf("only version 2 VHDs are supported, found version: %d", parameters.Version)
}
if parameters.Version2.GetInfoOnly {
getInfoOnly = 1
}
if parameters.Version2.ReadOnly {
readOnly = 1
}
params := &openVirtualDiskParameters{
version: parameters.Version,
version2: openVersion2{
getInfoOnly,
readOnly,
parameters.Version2.ResiliencyGUID,
},
}
if err := openVirtualDisk(
&defaultType,
vhdPath,
uint32(virtualDiskAccessMask),
uint32(openVirtualDiskFlags),
params,
&handle,
); err != nil {
return 0, fmt.Errorf("failed to open virtual disk: %w", err)
}
return handle, nil
}
// CreateVirtualDisk creates a virtual harddisk and returns a handle to the disk.
func CreateVirtualDisk(
path string,
virtualDiskAccessMask VirtualDiskAccessMask,
createVirtualDiskFlags CreateVirtualDiskFlag,
parameters *CreateVirtualDiskParameters,
) (syscall.Handle, error) {
var (
handle syscall.Handle
defaultType VirtualStorageType
)
if parameters.Version != 2 {
return handle, fmt.Errorf("only version 2 VHDs are supported, found version: %d", parameters.Version)
}
if err := createVirtualDisk(
&defaultType,
path,
uint32(virtualDiskAccessMask),
nil,
uint32(createVirtualDiskFlags),
0,
parameters,
nil,
&handle,
); err != nil {
return handle, fmt.Errorf("failed to create virtual disk: %w", err)
}
return handle, nil
}
// GetVirtualDiskPhysicalPath takes a handle to a virtual hard disk and returns the physical
// path of the disk on the machine. This path is in the form \\.\PhysicalDriveX where X is an integer
// that represents the particular enumeration of the physical disk on the caller's system.
func GetVirtualDiskPhysicalPath(handle syscall.Handle) (_ string, err error) {
var (
diskPathSizeInBytes uint32 = 256 * 2 // max path length 256 wide chars
diskPhysicalPathBuf [256]uint16
)
if err := getVirtualDiskPhysicalPath(
handle,
&diskPathSizeInBytes,
&diskPhysicalPathBuf[0],
); err != nil {
return "", fmt.Errorf("failed to get disk physical path: %w", err)
}
return windows.UTF16ToString(diskPhysicalPathBuf[:]), nil
}
// CreateDiffVhd is a helper function to create a differencing virtual disk.
//
//revive:disable-next-line:var-naming VHD, not Vhd
func CreateDiffVhd(diffVhdPath, baseVhdPath string, blockSizeInMB uint32) error {
// Setting `ParentPath` is how to signal to create a differencing disk.
createParams := &CreateVirtualDiskParameters{
Version: 2,
Version2: CreateVersion2{
ParentPath: windows.StringToUTF16Ptr(baseVhdPath),
BlockSizeInBytes: blockSizeInMB * 1024 * 1024,
OpenFlags: uint32(OpenVirtualDiskFlagCachedIO),
},
}
vhdHandle, err := CreateVirtualDisk(
diffVhdPath,
VirtualDiskAccessNone,
CreateVirtualDiskFlagNone,
createParams,
)
if err != nil {
return fmt.Errorf("failed to create differencing vhd: %w", err)
}
if err := syscall.CloseHandle(vhdHandle); err != nil {
return fmt.Errorf("failed to close differencing vhd handle: %w", err)
}
return nil
}

View File

@@ -1,105 +0,0 @@
//go:build windows
// Code generated by 'go generate' using "github.com/Microsoft/go-winio/tools/mkwinsyscall"; DO NOT EDIT.
package vhd
import (
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
var _ unsafe.Pointer
// Do the interface allocations only once for common
// Errno values.
const (
errnoERROR_IO_PENDING = 997
)
var (
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING)
errERROR_EINVAL error = syscall.EINVAL
)
// errnoErr returns common boxed Errno values, to prevent
// allocations at runtime.
func errnoErr(e syscall.Errno) error {
switch e {
case 0:
return errERROR_EINVAL
case errnoERROR_IO_PENDING:
return errERROR_IO_PENDING
}
return e
}
var (
modvirtdisk = windows.NewLazySystemDLL("virtdisk.dll")
procAttachVirtualDisk = modvirtdisk.NewProc("AttachVirtualDisk")
procCreateVirtualDisk = modvirtdisk.NewProc("CreateVirtualDisk")
procDetachVirtualDisk = modvirtdisk.NewProc("DetachVirtualDisk")
procGetVirtualDiskPhysicalPath = modvirtdisk.NewProc("GetVirtualDiskPhysicalPath")
procOpenVirtualDisk = modvirtdisk.NewProc("OpenVirtualDisk")
)
func attachVirtualDisk(handle syscall.Handle, securityDescriptor *uintptr, attachVirtualDiskFlag uint32, providerSpecificFlags uint32, parameters *AttachVirtualDiskParameters, overlapped *syscall.Overlapped) (win32err error) {
r0, _, _ := syscall.SyscallN(procAttachVirtualDisk.Addr(), uintptr(handle), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(attachVirtualDiskFlag), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(overlapped)))
if r0 != 0 {
win32err = syscall.Errno(r0)
}
return
}
func createVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (win32err error) {
var _p0 *uint16
_p0, win32err = syscall.UTF16PtrFromString(path)
if win32err != nil {
return
}
return _createVirtualDisk(virtualStorageType, _p0, virtualDiskAccessMask, securityDescriptor, createVirtualDiskFlags, providerSpecificFlags, parameters, overlapped, handle)
}
func _createVirtualDisk(virtualStorageType *VirtualStorageType, path *uint16, virtualDiskAccessMask uint32, securityDescriptor *uintptr, createVirtualDiskFlags uint32, providerSpecificFlags uint32, parameters *CreateVirtualDiskParameters, overlapped *syscall.Overlapped, handle *syscall.Handle) (win32err error) {
r0, _, _ := syscall.SyscallN(procCreateVirtualDisk.Addr(), uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(unsafe.Pointer(securityDescriptor)), uintptr(createVirtualDiskFlags), uintptr(providerSpecificFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(handle)))
if r0 != 0 {
win32err = syscall.Errno(r0)
}
return
}
func detachVirtualDisk(handle syscall.Handle, detachVirtualDiskFlags uint32, providerSpecificFlags uint32) (win32err error) {
r0, _, _ := syscall.SyscallN(procDetachVirtualDisk.Addr(), uintptr(handle), uintptr(detachVirtualDiskFlags), uintptr(providerSpecificFlags))
if r0 != 0 {
win32err = syscall.Errno(r0)
}
return
}
func getVirtualDiskPhysicalPath(handle syscall.Handle, diskPathSizeInBytes *uint32, buffer *uint16) (win32err error) {
r0, _, _ := syscall.SyscallN(procGetVirtualDiskPhysicalPath.Addr(), uintptr(handle), uintptr(unsafe.Pointer(diskPathSizeInBytes)), uintptr(unsafe.Pointer(buffer)))
if r0 != 0 {
win32err = syscall.Errno(r0)
}
return
}
func openVirtualDisk(virtualStorageType *VirtualStorageType, path string, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *openVirtualDiskParameters, handle *syscall.Handle) (win32err error) {
var _p0 *uint16
_p0, win32err = syscall.UTF16PtrFromString(path)
if win32err != nil {
return
}
return _openVirtualDisk(virtualStorageType, _p0, virtualDiskAccessMask, openVirtualDiskFlags, parameters, handle)
}
func _openVirtualDisk(virtualStorageType *VirtualStorageType, path *uint16, virtualDiskAccessMask uint32, openVirtualDiskFlags uint32, parameters *openVirtualDiskParameters, handle *syscall.Handle) (win32err error) {
r0, _, _ := syscall.SyscallN(procOpenVirtualDisk.Addr(), uintptr(unsafe.Pointer(virtualStorageType)), uintptr(unsafe.Pointer(path)), uintptr(virtualDiskAccessMask), uintptr(openVirtualDiskFlags), uintptr(unsafe.Pointer(parameters)), uintptr(unsafe.Pointer(handle)))
if r0 != 0 {
win32err = syscall.Errno(r0)
}
return
}

View File

@@ -1,12 +0,0 @@
Language: Cpp
BasedOnStyle: Microsoft
BreakBeforeBraces: Attach
PointerAlignment: Left
AllowShortFunctionsOnASingleLine: All
# match Go style
IndentCaseLabels: false
# don't break comments over line limit (needed for CodeQL exceptions)
ReflowComments: false
InsertNewlineAtEOF: true
KeepEmptyLines:
AtEndOfFile: true

View File

@@ -1,3 +0,0 @@
* text=auto eol=lf
vendor/** -text
test/vendor/** -text

View File

@@ -1,53 +0,0 @@
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Ignore vscode setting files
.vscode/
.idea/
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
# Ignore gcs bin directory
service/bin/
service/pkg/
*.img
*.vhd
*.tar.gz
*.tar
# Make stuff
.rootfs-done
bin/*
rootfs/*
rootfs-conv/*
*.o
/build/
deps/*
out/*
# protobuf files
# only files at root of the repo, otherwise this will cause issues with vendoring
/protobuf/*
# test results
test/results
# go workspace files
go.work
go.work.sum
# keys and related artifacts
*.pem
*.cose

View File

@@ -1,173 +0,0 @@
run:
timeout: 8m
tests: true
build-tags:
- admin
- functional
- integration
linters:
enable:
# defaults:
# - errcheck
# - gosimple
# - govet
# - ineffassign
# - staticcheck
# - typecheck
# - unused
- errorlint # error wrapping (eg, not using `errors.Is`, using `%s` instead of `%w` in `fmt.Errorf`)
- gofmt # whether code was gofmt-ed
- govet # enabled by default, but just to be sure
- nolintlint # ill-formed or insufficient nolint directives
- stylecheck # golint replacement
- thelper # test helpers without t.Helper()
linters-settings:
govet:
enable-all: true
disable:
# struct order is often for Win32 compat
# also, ignore pointer bytes/GC issues for now until performance becomes an issue
- fieldalignment
stylecheck:
# https://staticcheck.io/docs/checks
checks: ["all"]
issues:
exclude-dirs:
# paths are relative to module root
- cri-containerd/test-images
exclude-rules:
# err is very often shadowed in nested scopes
- linters:
- govet
text: '^shadow: declaration of "err" shadows declaration'
# path is relative to module root, which is ./test/
- path: cri-containerd
linters:
- stylecheck
text: "^ST1003: should not use underscores in package names$"
source: "^package cri_containerd$"
# don't bother with propper error wrapping in test code
- path: cri-containerd
linters:
- errorlint
text: "non-wrapping format verb for fmt.Errorf"
# This repo has a LOT of generated schema files, operating system bindings, and other
# things that ST1003 from stylecheck won't like (screaming case Windows api constants for example).
# There's also some structs that we *could* change the initialisms to be Go friendly
# (Id -> ID) but they're exported and it would be a breaking change.
# This makes it so that most new code, code that isn't supposed to be a pretty faithful
# mapping to an OS call/constants, or non-generated code still checks if we're following idioms,
# while ignoring the things that are just noise or would be more of a hassle than it'd be worth to change.
- path: layer.go
linters:
- stylecheck
text: "ST1003:"
- path: hcsshim.go
linters:
- stylecheck
text: "ST1003:"
- path: cmd\\ncproxy\\nodenetsvc\\
linters:
- stylecheck
text: "ST1003:"
- path: cmd\\ncproxy_mock\\
linters:
- stylecheck
text: "ST1003:"
- path: internal\\hcs\\schema2\\
linters:
- stylecheck
- gofmt
- path: internal\\wclayer\\
linters:
- stylecheck
text: "ST1003:"
- path: hcn\\
linters:
- stylecheck
text: "ST1003:"
- path: internal\\hcs\\schema1\\
linters:
- stylecheck
text: "ST1003:"
- path: internal\\hns\\
linters:
- stylecheck
text: "ST1003:"
- path: ext4\\internal\\compactext4\\
linters:
- stylecheck
text: "ST1003:"
- path: ext4\\internal\\format\\
linters:
- stylecheck
text: "ST1003:"
- path: internal\\guestrequest\\
linters:
- stylecheck
text: "ST1003:"
- path: internal\\guest\\prot\\
linters:
- stylecheck
text: "ST1003:"
- path: internal\\windevice\\
linters:
- stylecheck
text: "ST1003:"
- path: internal\\winapi\\
linters:
- stylecheck
text: "ST1003:"
- path: internal\\vmcompute\\
linters:
- stylecheck
text: "ST1003:"
- path: internal\\regstate\\
linters:
- stylecheck
text: "ST1003:"
- path: internal\\hcserror\\
linters:
- stylecheck
text: "ST1003:"
# v0 APIs are deprecated, but still retained for backwards compatability
- path: cmd\\ncproxy\\
linters:
- staticcheck
text: "^SA1019: .*(ncproxygrpc|nodenetsvc)[/]?v0"
- path: internal\\tools\\networkagent
linters:
- staticcheck
text: "^SA1019: .*nodenetsvc[/]?v0"
- path: internal\\vhdx\\info
linters:
- stylecheck
text: "ST1003:"

Some files were not shown because too many files have changed in this diff Show More