Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor meta.jq to prepare for signing and oci-import implementation #17

Merged
merged 1 commit into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 54 additions & 6 deletions .test/example-commands.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,49 @@

# </pull>
# <build>
SOURCE_DATE_EPOCH=1700741054 docker buildx build --progress=plain --provenance=mode=max --sbom=generator="$BASHBREW_BUILDKIT_SBOM_GENERATOR" --output '"type=oci","dest=temp.tar","annotation.org.opencontainers.image.source=https://github.com/docker-library/docker.git#6d541d27b5dd12639e5a33a675ebca04d3837d74:24/cli","annotation-manifest-descriptor.org.opencontainers.image.source=https://github.com/docker-library/docker.git#6d541d27b5dd12639e5a33a675ebca04d3837d74:24/cli","annotation.org.opencontainers.image.revision=6d541d27b5dd12639e5a33a675ebca04d3837d74","annotation-manifest-descriptor.org.opencontainers.image.revision=6d541d27b5dd12639e5a33a675ebca04d3837d74","annotation.org.opencontainers.image.version=24.0.7-cli","annotation-manifest-descriptor.org.opencontainers.image.version=24.0.7-cli","annotation.org.opencontainers.image.url=https://hub.docker.com/_/docker","annotation-manifest-descriptor.org.opencontainers.image.url=https://hub.docker.com/_/docker"' --tag 'docker:24.0.7-cli' --tag 'docker:24.0-cli' --tag 'docker:24-cli' --tag 'docker:cli' --tag 'docker:24.0.7-cli-alpine3.18' --tag 'oisupport/staging-amd64:4b199ac326c74b3058a147e14f553af9e8e1659abc29bd3e82c9c9807b66ee43' --platform 'linux/amd64' --build-context 'alpine:3.18=docker-image://alpine:3.18@sha256:d695c3de6fcd8cfe3a6222b0358425d40adfd129a8a47c3416faff1a8aece389' --build-arg BUILDKIT_SYNTAX="$BASHBREW_BUILDKIT_SYNTAX" --file 'Dockerfile' 'https://github.com/docker-library/docker.git#6d541d27b5dd12639e5a33a675ebca04d3837d74:24/cli'
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is much cuter with --ignore-all-space --word-diff:

Diff:

image

diff --git a/.test/example-commands.sh b/.test/example-commands.sh
index f165244..4bcbaf8 100644
--- a/.test/example-commands.sh
+++ b/.test/example-commands.sh
@@ -3,15 +3,49 @@

# </pull>
# <build>
SOURCE_DATE_EPOCH=1700741054 {+\+}
	docker buildx build --progress=plain {+\+}
	--provenance=mode=max {+\+}
	--sbom=generator="$BASHBREW_BUILDKIT_SBOM_GENERATOR" {+\+}
	--output [-'"type=oci","dest=temp.tar","annotation.org.opencontainers.image.source=https://github.com/docker-library/docker.git#6d541d27b5dd12639e5a33a675ebca04d3837d74:24/cli","annotation-manifest-descriptor.org.opencontainers.image.source=https://github.com/docker-library/docker.git#6d541d27b5dd12639e5a33a675ebca04d3837d74:24/cli","annotation.org.opencontainers.image.revision=6d541d27b5dd12639e5a33a675ebca04d3837d74","annotation-manifest-descriptor.org.opencontainers.image.revision=6d541d27b5dd12639e5a33a675ebca04d3837d74","annotation.org.opencontainers.image.version=24.0.7-cli","annotation-manifest-descriptor.org.opencontainers.image.version=24.0.7-cli","annotation.org.opencontainers.image.url=https://hub.docker.com/_/docker","annotation-manifest-descriptor.org.opencontainers.image.url=https://hub.docker.com/_/docker"'-]{+'"type=oci","dest=temp.tar"' \+}
{+	--annotation 'org.opencontainers.image.source=https://github.com/docker-library/docker.git#6d541d27b5dd12639e5a33a675ebca04d3837d74:24/cli' \+}
{+	--annotation 'org.opencontainers.image.revision=6d541d27b5dd12639e5a33a675ebca04d3837d74' \+}
{+	--annotation 'org.opencontainers.image.created=2023-11-23T12:04:14Z' \+}
{+	--annotation 'org.opencontainers.image.version=24.0.7-cli' \+}
{+	--annotation 'org.opencontainers.image.url=https://hub.docker.com/_/docker' \+}
{+	--annotation 'manifest-descriptor:org.opencontainers.image.source=https://github.com/docker-library/docker.git#6d541d27b5dd12639e5a33a675ebca04d3837d74:24/cli' \+}
{+	--annotation 'manifest-descriptor:org.opencontainers.image.revision=6d541d27b5dd12639e5a33a675ebca04d3837d74' \+}
{+	--annotation 'manifest-descriptor:org.opencontainers.image.created=1970-01-01T00:00:00Z' \+}
{+	--annotation 'manifest-descriptor:org.opencontainers.image.version=24.0.7-cli' \+}
{+	--annotation 'manifest-descriptor:org.opencontainers.image.url=https://hub.docker.com/_/docker' \+}
	--tag 'docker:24.0.7-cli' {+\+}
	--tag 'docker:24.0-cli' {+\+}
	--tag 'docker:24-cli' {+\+}
	--tag 'docker:cli' {+\+}
	--tag 'docker:24.0.7-cli-alpine3.18' {+\+}
	--tag 'oisupport/staging-amd64:4b199ac326c74b3058a147e14f553af9e8e1659abc29bd3e82c9c9807b66ee43' {+\+}
	--platform 'linux/amd64' {+\+}
	--build-context 'alpine:3.18=docker-image://alpine:3.18@sha256:d695c3de6fcd8cfe3a6222b0358425d40adfd129a8a47c3416faff1a8aece389' {+\+}
	--build-arg BUILDKIT_SYNTAX="$BASHBREW_BUILDKIT_SYNTAX" {+\+}
	--file 'Dockerfile' {+\+}
	'https://github.com/docker-library/docker.git#6d541d27b5dd12639e5a33a675ebca04d3837d74:24/cli'[-# </build>-]
[-# <push>-]
mkdir temp
tar -xvf temp.tar -C temp
{+rm temp.tar+}
jq [-'.manifests-]{+'+}
{+	.manifests+} |= [-(del(.[].annotations)-]{+(+}
{+		del(.[].annotations)+}
		| [-unique)'-]{+unique+}
{+		| if length != 1 then+}
{+			error("unexpected number of manifests: " + length)+}
{+		else . end+}
{+	)+}
{+'+} temp/index.json > temp/index.json.new
mv temp/index.json.new temp/index.json
{+# </build>+}
{+# <push>+}
crane push temp 'oisupport/staging-amd64:4b199ac326c74b3058a147e14f553af9e8e1659abc29bd3e82c9c9807b66ee43'
rm -rf temp[-temp.tar-]
# </push>

# docker:24.0.7-windowsservercore-ltsc2022 [windows-amd64]
@@ -20,7 +54,21 @@ docker pull 'mcr.microsoft.com/windows/servercore:ltsc2022@sha256:d4ab2dd7d3d0fc
docker tag 'mcr.microsoft.com/windows/servercore:ltsc2022@sha256:d4ab2dd7d3d0fce6edc5df459565a4c96bbb1d0148065b215ab5ddcab1e42eb4' 'mcr.microsoft.com/windows/servercore:ltsc2022'
# </pull>
# <build>
SOURCE_DATE_EPOCH=1700741054 {+\+}
	DOCKER_BUILDKIT=0 {+\+}
	docker build {+\+}
	--tag 'docker:24.0.7-windowsservercore-ltsc2022' {+\+}
	--tag 'docker:24.0-windowsservercore-ltsc2022' {+\+}
	--tag 'docker:24-windowsservercore-ltsc2022' {+\+}
	--tag 'docker:windowsservercore-ltsc2022' {+\+}
	--tag 'docker:24.0.7-windowsservercore' {+\+}
	--tag 'docker:24.0-windowsservercore' {+\+}
	--tag 'docker:24-windowsservercore' {+\+}
	--tag 'docker:windowsservercore' {+\+}
	--tag 'oisupport/staging-windows-amd64:9b405cfa5b88ba65121aabdb95ae90fd2e1fee7582174de82ae861613ae3072e' {+\+}
	--platform 'windows/amd64' {+\+}
	--file 'Dockerfile' {+\+}
	'https://github.com/docker-library/docker.git#6d541d27b5dd12639e5a33a675ebca04d3837d74:24/windows/windowsservercore-ltsc2022'
# </build>
# <push>
docker push 'oisupport/staging-windows-amd64:9b405cfa5b88ba65121aabdb95ae90fd2e1fee7582174de82ae861613ae3072e'

# </build>
# <push>
SOURCE_DATE_EPOCH=1700741054 \
docker buildx build --progress=plain \
--provenance=mode=max \
--sbom=generator="$BASHBREW_BUILDKIT_SBOM_GENERATOR" \
--output '"type=oci","dest=temp.tar"' \
--annotation 'org.opencontainers.image.source=https://github.com/docker-library/docker.git#6d541d27b5dd12639e5a33a675ebca04d3837d74:24/cli' \
--annotation 'org.opencontainers.image.revision=6d541d27b5dd12639e5a33a675ebca04d3837d74' \
--annotation 'org.opencontainers.image.created=2023-11-23T12:04:14Z' \
--annotation 'org.opencontainers.image.version=24.0.7-cli' \
--annotation 'org.opencontainers.image.url=https://hub.docker.com/_/docker' \
--annotation 'manifest-descriptor:org.opencontainers.image.source=https://github.com/docker-library/docker.git#6d541d27b5dd12639e5a33a675ebca04d3837d74:24/cli' \
--annotation 'manifest-descriptor:org.opencontainers.image.revision=6d541d27b5dd12639e5a33a675ebca04d3837d74' \
--annotation 'manifest-descriptor:org.opencontainers.image.created=1970-01-01T00:00:00Z' \
--annotation 'manifest-descriptor:org.opencontainers.image.version=24.0.7-cli' \
--annotation 'manifest-descriptor:org.opencontainers.image.url=https://hub.docker.com/_/docker' \
--tag 'docker:24.0.7-cli' \
--tag 'docker:24.0-cli' \
--tag 'docker:24-cli' \
--tag 'docker:cli' \
--tag 'docker:24.0.7-cli-alpine3.18' \
--tag 'oisupport/staging-amd64:4b199ac326c74b3058a147e14f553af9e8e1659abc29bd3e82c9c9807b66ee43' \
--platform 'linux/amd64' \
--build-context 'alpine:3.18=docker-image://alpine:3.18@sha256:d695c3de6fcd8cfe3a6222b0358425d40adfd129a8a47c3416faff1a8aece389' \
--build-arg BUILDKIT_SYNTAX="$BASHBREW_BUILDKIT_SYNTAX" \
--file 'Dockerfile' \
'https://github.com/docker-library/docker.git#6d541d27b5dd12639e5a33a675ebca04d3837d74:24/cli'
mkdir temp
tar -xvf temp.tar -C temp
jq '.manifests |= (del(.[].annotations) | unique)' temp/index.json > temp/index.json.new
rm temp.tar
jq '
.manifests |= (
del(.[].annotations)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are the annotations deleted?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

@whalelines whalelines Jan 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then why do we add them in the build? (Sorry for my ignorance)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So that they show up in the provenance data 😅

| unique
| if length != 1 then
error("unexpected number of manifests: " + length)
else . end
)
' temp/index.json > temp/index.json.new
mv temp/index.json.new temp/index.json
# </build>
# <push>
crane push temp 'oisupport/staging-amd64:4b199ac326c74b3058a147e14f553af9e8e1659abc29bd3e82c9c9807b66ee43'
rm -rf temp temp.tar
rm -rf temp
# </push>

# docker:24.0.7-windowsservercore-ltsc2022 [windows-amd64]
Expand All @@ -20,7 +54,21 @@ docker pull 'mcr.microsoft.com/windows/servercore:ltsc2022@sha256:d4ab2dd7d3d0fc
docker tag 'mcr.microsoft.com/windows/servercore:ltsc2022@sha256:d4ab2dd7d3d0fce6edc5df459565a4c96bbb1d0148065b215ab5ddcab1e42eb4' 'mcr.microsoft.com/windows/servercore:ltsc2022'
# </pull>
# <build>
SOURCE_DATE_EPOCH=1700741054 DOCKER_BUILDKIT=0 docker build --tag 'docker:24.0.7-windowsservercore-ltsc2022' --tag 'docker:24.0-windowsservercore-ltsc2022' --tag 'docker:24-windowsservercore-ltsc2022' --tag 'docker:windowsservercore-ltsc2022' --tag 'docker:24.0.7-windowsservercore' --tag 'docker:24.0-windowsservercore' --tag 'docker:24-windowsservercore' --tag 'docker:windowsservercore' --tag 'oisupport/staging-windows-amd64:9b405cfa5b88ba65121aabdb95ae90fd2e1fee7582174de82ae861613ae3072e' --platform 'windows/amd64' --file 'Dockerfile' 'https://github.com/docker-library/docker.git#6d541d27b5dd12639e5a33a675ebca04d3837d74:24/windows/windowsservercore-ltsc2022'
SOURCE_DATE_EPOCH=1700741054 \
DOCKER_BUILDKIT=0 \
docker build \
--tag 'docker:24.0.7-windowsservercore-ltsc2022' \
--tag 'docker:24.0-windowsservercore-ltsc2022' \
--tag 'docker:24-windowsservercore-ltsc2022' \
--tag 'docker:windowsservercore-ltsc2022' \
--tag 'docker:24.0.7-windowsservercore' \
--tag 'docker:24.0-windowsservercore' \
--tag 'docker:24-windowsservercore' \
--tag 'docker:windowsservercore' \
--tag 'oisupport/staging-windows-amd64:9b405cfa5b88ba65121aabdb95ae90fd2e1fee7582174de82ae861613ae3072e' \
--platform 'windows/amd64' \
--file 'Dockerfile' \
'https://github.com/docker-library/docker.git#6d541d27b5dd12639e5a33a675ebca04d3837d74:24/windows/windowsservercore-ltsc2022'
# </build>
# <push>
docker push 'oisupport/staging-windows-amd64:9b405cfa5b88ba65121aabdb95ae90fd2e1fee7582174de82ae861613ae3072e'
Expand Down
2 changes: 1 addition & 1 deletion .test/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ time "$dir/../sources.sh" "$@" > "$dir/sources.json"
time "$dir/../builds.sh" --cache "$dir/cache-builds.json" "$dir/sources.json" > "$dir/builds.json"

# generate an "example commands" file so that changes to generated commands are easier to review
jq -r -L "$dir/.." '
SOURCE_DATE_EPOCH=0 jq -r -L "$dir/.." '
include "meta";
[
first(.[] | select(normalized_builder == "buildkit")),
Expand Down
185 changes: 125 additions & 60 deletions meta.jq
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,26 @@ def normalized_builder:
end
else . end
;
def docker_uses_containerd_storage:
# TODO somehow detect docker-with-containerd-storage
false
;
# input: "build" object (with "buildId" top level key)
# output: boolean
def should_use_docker_buildx_driver:
normalized_builder == "buildkit"
and (
.build.arch as $arch
# bashbrew remote arches --json tianon/buildkit:0.12 | jq '.arches | keys_unsorted' -c
| ["amd64","arm32v5","arm32v6","arm32v7","arm64v8","i386","mips64le","ppc64le","riscv64","s390x"]
# TODO this needs to be based on the *host* architecture, not the *target* architecture (amd64 vs i386)
| index($arch)
| not
docker_uses_containerd_storage
or (
.build.arch as $arch
# bashbrew remote arches --json tianon/buildkit:0.12 | jq '.arches | keys_unsorted' -c
| ["amd64","arm32v5","arm32v6","arm32v7","arm64v8","i386","mips64le","ppc64le","riscv64","s390x"]
# TODO this needs to be based on the *host* architecture, not the *target* architecture (amd64 vs i386)
| index($arch)
| not
# TODO "failed to read dockerfile: failed to load cache key: subdir not supported yet" asdflkjalksdjfklasdjfklajsdklfjasdklgfnlkasdfgbhnkljasdhgouiahsdoifjnask,.dfgnklasdbngoikasdhfoiasjdklfjasdlkfjalksdjfkladshjflikashdbgiohasdfgiohnaskldfjhnlkasdhfnklasdhglkahsdlfkjasdlkfjadsklfjsdl (hence "tianon/buildkit" instead of "moby/buildkit"; need *all* the arches we care about/support for consistent support)
)
)
# TODO "failed to read dockerfile: failed to load cache key: subdir not supported yet" asdflkjalksdjfklasdjfklajsdklfjasdklgfnlkasdfgbhnkljasdhgouiahsdoifjnask,.dfgnklasdbngoikasdhfoiasjdklfjasdlkfjalksdjfkladshjflikashdbgiohasdfgiohnaskldfjhnlkasdhfnklasdhglkahsdlfkjasdlkfjadsklfjsdl (hence "tianon/buildkit" instead of "moby/buildkit")
;
# input: "build" object (with "buildId" top level key)
# output: string "pull command" ("docker pull ..."), may be multiple lines, expects to run in Bash with "set -Eeuo pipefail", might be empty
Expand Down Expand Up @@ -71,66 +78,110 @@ def git_build_url:
) + "#" + .GitCommit + ":" + .Directory
;
# input: "build" object (with "buildId" top level key)
# output: map of annotations to set
def build_annotations($buildUrl):
{
# https://github.com/opencontainers/image-spec/blob/v1.1.0-rc4/annotations.md#pre-defined-annotation-keys
"org.opencontainers.image.source": $buildUrl,
"org.opencontainers.image.revision": .source.entry.GitCommit,
"org.opencontainers.image.created": (.source.entry.SOURCE_DATE_EPOCH | strftime("%FT%TZ")), # see notes below about image index vs image manifest

# TODO come up with less assuming values here? (Docker Hub assumption, tag ordering assumption)
"org.opencontainers.image.version": ( # value of the first image tag
first(.source.allTags[] | select(contains(":")))
| sub("^.*:"; "")
# TODO maybe we should do the first, longest, non-latest tag instead of just the first tag?
),
"org.opencontainers.image.url": ( # URL to Docker Hub
first(.source.allTags[] | select(contains(":")))
| sub(":.*$"; "")
| if contains("/") then
"r/" + .
else
"_/" + .
end
| "https://hub.docker.com/" + .
),

# TODO org.opencontainers.image.vendor ? (feels leaky to put "Docker Official Images" here when this is all otherwise mostly generic)
}
| with_entries(select(.value)) # strip off anything missing a value (possibly "source", "url", "version", etc)
;
def build_annotations:
build_annotations(git_build_url)
;
# input: multi-line string with indentation and comments
# output: multi-line string with less indentation and no comments
def unindent_and_decomment_jq($indents):
# trim out comment lines and unnecessary indentation
gsub("(?m)^(\t+#[^\n]*\n?|\t{\($indents)}(?<extra>.*)$)"; "\(.extra // "")")
# trim out empty lines
| gsub("\n\n+"; "\n")
;
# input: "build" object (with "buildId" top level key)
# output: string "build command" ("docker buildx build ..."), may be multiple lines, expects to run in Bash with "set -Eeuo pipefail"
def build_command:
normalized_builder as $builder
| git_build_url as $buildUrl
| if $builder == "buildkit" then
[
git_build_url as $buildUrl
| (
(should_use_docker_buildx_driver | not)
or docker_uses_containerd_storage
) as $supportsAnnotationsAndAttestsations
| [
(
[
@sh "SOURCE_DATE_EPOCH=\(.source.entry.SOURCE_DATE_EPOCH)",
# TODO EXPERIMENTAL_BUILDKIT_SOURCE_POLICY=<(jq ...)
"docker buildx build --progress=plain",
if should_use_docker_buildx_driver then "--load" else # TODO if we get containerd integration and thus use "--load" unconditionally again, we should update this to still set annotations! (and still gate SBOMs on appropriate scanner-supported architectures)
if $supportsAnnotationsAndAttestsations then
"--provenance=mode=max",
# see "bashbrew remote arches docker/scout-sbom-indexer:1" (we need the SBOM scanner to be runnable on the host architecture)
# bashbrew remote arches --json docker/scout-sbom-indexer:1 | jq '.arches | keys_unsorted' -c
if .build.arch as $arch | ["amd64","arm32v5","arm32v7","arm64v8","i386","ppc64le","riscv64","s390x"] | index($arch) then
# TODO this needs to be based on the *host* architecture, not the *target* architecture (amd64 vs i386)
"--sbom=generator=\"$BASHBREW_BUILDKIT_SBOM_GENERATOR\""
# TODO this should also be totally optional -- for example, Tianon doesn't want SBOMs on his personal images
else empty end,
(
"--output " + (
[
"type=oci", # TODO find a better way to build/tag with a full list of tags but only actually *push* to one of them so we don't have to round-trip through containerd
"dest=temp.tar", # TODO choose/find a good "safe" place to put this (temporarily)
(
{
# https://github.com/opencontainers/image-spec/blob/v1.1.0-rc4/annotations.md#pre-defined-annotation-keys
"org.opencontainers.image.source": $buildUrl,
"org.opencontainers.image.revision": .source.entry.GitCommit,

# TODO come up with less assuming values here? (Docker Hub assumption, tag ordering assumption)
"org.opencontainers.image.version": ( # value of the first image tag
first(.source.allTags[] | select(contains(":")))
| sub("^.*:"; "")
# TODO maybe we should do the first, longest, non-latest tag instead of just the first tag?
),
"org.opencontainers.image.url": ( # URL to Docker Hub
first(.source.allTags[] | select(contains(":")))
| sub(":.*$"; "")
| if contains("/") then
"r/" + .
else
"_/" + .
end
| "https://hub.docker.com/" + .
),
# TODO org.opencontainers.image.vendor ? (feels leaky to put "Docker Official Images" here when this is all otherwise mostly generic)
}
| to_entries[] | select(.value != null) |
"annotation." + .key + "=" + .value,
"annotation-manifest-descriptor." + .key + "=" + .value
),
empty
]
| @csv
| @sh
)
),
empty
end,
else empty end,
"--output " + (
[
if should_use_docker_buildx_driver then
"type=docker"
else
"type=oci",
"dest=temp.tar", # TODO choose/find a good "safe" place to put this (temporarily)
empty
end,
empty
]
| @csv
| @sh
),
(
if $supportsAnnotationsAndAttestsations then
build_annotations($buildUrl)
| to_entries
# separate loops so that "image manifest" annotations are grouped separate from the index/descriptor annotations (easier to read)
| (
.[]
| @sh "--annotation \(.key + "=" + .value)"
),
(
.[]
| @sh "--annotation \(
"manifest-descriptor:" + .key + "="
+ if .key == "org.opencontainers.image.created" then
# the "current" time breaks reproducibility (for the purposes of build verification), so we put "now" in the image index but "SOURCE_DATE_EPOCH" in the image manifest (which is the thing we'd ideally like to have reproducible, eventually)
(env.SOURCE_DATE_EPOCH // now) | tonumber | strftime("%FT%TZ")
# (this assumes the actual build is going to happen shortly after generating the command)
else .value end
)",
empty
)
else empty end
),
(
.source.arches[].tags[],
.source.arches[].archTags[],
Expand All @@ -148,8 +199,26 @@ def build_command:
@sh "--file \(.source.entry.File)",
($buildUrl | @sh),
empty
] | join(" ")
] | join(" \\\n\t")
),
if should_use_docker_buildx_driver then empty else
# munge the tarball into a suitable "oci layout" directory (ready for "crane push")
"mkdir temp",
"tar -xvf temp.tar -C temp",
"rm temp.tar",
# munge the index to what crane wants ("Error: layout contains 5 entries, consider --index")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps something to do with this?

Copy link
Member Author

@tianon tianon Jan 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct -- because of the limitations of an OCI layout as a docker save format, the output from docker buildx build --output type=tar generates an index.json with a new entry for every --tag flag:

$ docker buildx build --builder foo --quiet --output type=oci - <<<$'FROM scratch\nCMD []' --tag foo --tag bar --tag baz | tar --extract --to-stdout index.json | jq
{
  "schemaVersion": 2,
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:1be03c71b27e7950b1a52e029ad139847a0771de9d2705ae8ab3742cd7d91ad6",
      "size": 288,
      "annotations": {
        "io.containerd.image.name": "docker.io/library/foo:latest",
        "org.opencontainers.image.created": "2024-01-26T18:48:33Z",
        "org.opencontainers.image.ref.name": "latest"
      },
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:1be03c71b27e7950b1a52e029ad139847a0771de9d2705ae8ab3742cd7d91ad6",
      "size": 288,
      "annotations": {
        "io.containerd.image.name": "docker.io/library/bar:latest",
        "org.opencontainers.image.created": "2024-01-26T18:48:33Z",
        "org.opencontainers.image.ref.name": "latest"
      },
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:1be03c71b27e7950b1a52e029ad139847a0771de9d2705ae8ab3742cd7d91ad6",
      "size": 288,
      "annotations": {
        "io.containerd.image.name": "docker.io/library/baz:latest",
        "org.opencontainers.image.created": "2024-01-26T18:48:33Z",
        "org.opencontainers.image.ref.name": "latest"
      },
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      }
    }
  ]
}

Because the annotations are where the unique tags are stored, if we delete those, then unique can give us a single entry instead:

$ docker buildx build --builder foo --quiet --output type=oci - <<<$'FROM scratch\nCMD []' --tag foo --tag bar --tag baz | tar --extract --to-stdout index.json | jq '.manifests |= (del(.[].annotations) | unique)'
{
  "schemaVersion": 2,
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "digest": "sha256:1be03c71b27e7950b1a52e029ad139847a0771de9d2705ae8ab3742cd7d91ad6",
      "size": 288,
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      }
    }
  ]
}

And now again, but this time with --provenance turned on (so we get an image index instead of an image manifest -- note the mediaType field in the descriptor):

$ docker buildx build --builder foo --provenance=mode=max --no-cache --quiet --output type=oci - <<<$'FROM hello-world' --tag foo --tag bar --tag baz | tar --extract --to-stdout index.json | jq '.manifests |= (del(.[].annotations) | unique)'
{
  "schemaVersion": 2,
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.index.v1+json",
      "digest": "sha256:a3cd4b5d34fcc7fe9ae1f6a9fd5459b7297e6cee7f6a5b200b715932a0fcb246",
      "size": 855
    }
  ]
}

@sh "jq \("
.manifests |= (
del(.[].annotations)
| unique
| if length != 1 then
error(\"unexpected number of manifests: \" + length)
else . end
)
" | unindent_and_decomment_jq(4)) temp/index.json > temp/index.json.new",
"mv temp/index.json.new temp/index.json",
empty
end,
# possible improvements in buildkit/buildx that could help us:
# - allowing OCI output directly to a directory instead of a tar (thus getting symmetry with the oci-layout:// inputs it can take)
# - allowing tag as one thing and push as something else, potentially mutually exclusive
Expand All @@ -158,7 +227,8 @@ def build_command:
empty
] | join("\n")
elif $builder == "classic" then
[
git_build_url as $buildUrl
| [
(
[
@sh "SOURCE_DATE_EPOCH=\(.source.entry.SOURCE_DATE_EPOCH)",
Expand All @@ -175,7 +245,7 @@ def build_command:
($buildUrl | @sh),
empty
]
| join(" ")
| join(" \\\n\t")
),
empty
] | join("\n")
Expand All @@ -199,14 +269,9 @@ def push_command:
@sh "docker push \(.build.img)"
elif $builder == "buildkit" then
[
# extract to a directory and "crane push" (easier to get correct than "ctr image import" + "ctr image push", especially with authentication)
"mkdir temp",
"tar -xvf temp.tar -C temp",
# munge the index to what crane wants ("Error: layout contains 5 entries, consider --index")
@sh "jq \(".manifests |= (del(.[].annotations) | unique)") temp/index.json > temp/index.json.new",
"mv temp/index.json.new temp/index.json",
# "crane push" is easier to get correct than "ctr image import" + "ctr image push", especially with authentication
@sh "crane push temp \(.build.img)",
"rm -rf temp temp.tar",
"rm -rf temp",
empty
] | join("\n")
elif $builder == "oci-import" then
Expand Down