Skip to content

Commit

Permalink
Refactor meta.jq to prepare for signing and oci-import implementa…
Browse files Browse the repository at this point in the history
…tion

- explicit "containerd image storage in dockerd" placeholder
- explicit "annotations" helper/generator (so they can be added in the `oci-import` builder later)
- setting the `org.opencontainers.image.created` annoation inside the image index to the actual build date (otherwise `SOURCE_DATE_EPOCH` makes it hard to tell an image is actually freshly built)
- more whitespace in generated commands for better readability
- extract OCI tar in the build step (so the "output" of the build is an OCI layout directory vs tarball) instead of the pull step (so we can more ergonomically add pre-push signing)
  • Loading branch information
tianon committed Jan 25, 2024
1 parent 0e21e01 commit bdc5f4d
Showing 1 changed file with 102 additions and 59 deletions.
161 changes: 102 additions & 59 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,95 @@ def git_build_url:
) + "#" + .GitCommit + ":" + .Directory
;
# input: "build" object (with "buildId" top level key)
# output: map of annotations to set
def build_annotations($buildUrl; created):
{
# 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)

# this should *only* be set in the image index (not in the image manifest)
"org.opencontainers.image.created": (created | strftime("%FT%TZ")),
# (this also assumes the actual build is going to happen shortly after generating this, in the case of using "now")
}
| with_entries(select(.value)) # strip off anything missing a value (possibly "source", "url", "version", etc)
;
def build_annotations($buildUrl):
build_annotations($buildUrl; now)
;
def build_annotations:
build_annotations(git_build_url)
;
# 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,
if $supportsAnnotationsAndAttestsations then
build_annotations($buildUrl)
| to_entries[]
| (
# the "current" time breaks reproducibility (for the purposes of build verification), so we only put it in the manifest descriptor (image index), *not* in the image manifest
if .key != "org.opencontainers.image.created" then
"annotation." + .key + "=" + .value
else empty end,
"annotation-manifest-descriptor." + .key + "=" + .value
)
else empty end,
empty
]
| @csv
| @sh
),
(
.source.arches[].tags[],
.source.arches[].archTags[],
Expand All @@ -148,8 +184,19 @@ 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")
@sh "jq \(".manifests |= (del(.[].annotations) | unique)") temp/index.json > temp/index.json.new",
# TODO validate length after unique
"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 +205,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 Down Expand Up @@ -199,14 +247,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

0 comments on commit bdc5f4d

Please sign in to comment.