Skip to content

Commit

Permalink
refactor: deduplicate images before pushing and signing
Browse files Browse the repository at this point in the history
  • Loading branch information
thesayyn authored and loosebazooka committed Feb 7, 2025
1 parent e072745 commit 2535a58
Show file tree
Hide file tree
Showing 7 changed files with 8,085 additions and 8,039 deletions.
2 changes: 1 addition & 1 deletion .bazelversion
Original file line number Diff line number Diff line change
@@ -1 +1 @@
7.2.0
7.4.0
5 changes: 5 additions & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ bazel_dep(name = "rules_oci", version = "1.7.5")
bazel_dep(name = "rules_distroless", version = "0.3.8")
bazel_dep(name = "rules_python", version = "0.35.0")

### OCI ###
oci = use_extension("@rules_oci//oci:extensions.bzl", "oci")
oci.toolchains(crane_version = "v0.18.0")
use_repo(oci, "oci_crane_toolchains")

### PYTHON ###
python = use_extension("@rules_python//python/extensions:python.bzl", "python")
python.toolchain(
Expand Down
15,936 changes: 7,968 additions & 7,968 deletions MODULE.bazel.lock

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions private/oci/digest.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"generate digest for oci_image and oci_image_index"

load("@aspect_bazel_lib//lib:copy_file.bzl", "copy_file")
load("@aspect_bazel_lib//lib:directory_path.bzl", "directory_path")
load("@aspect_bazel_lib//lib:jq.bzl", "jq")

# Normally we'd use the `.digest` target that rules_oci creates for every oci_image but
# we also use oci_image_index which does not have a digest target. This was fixed in
# https://github.com/bazel-contrib/rules_oci/pull/742 but it on the 2.x releases of rules_oci
# TODO: Remove this once we upgrade to rules_oci 2.x
def digest(name, image, **kwargs):
# `oci_image_rule` and `oci_image_index_rule` produce a directory as default output.
# Label for the [name]/index.json file
directory_path(
name = "_{}_index_json".format(name),
directory = image,
path = "index.json",
**kwargs
)

copy_file(
name = "_{}_index_json_cp".format(name),
src = "_{}_index_json".format(name),
out = "_{}_index.json".format(name),
**kwargs
)

# Matches the [name].digest target produced by rules_docker container_image
jq(
name = name,
args = ["--raw-output"],
srcs = ["_{}_index.json".format(name)],
filter = """.manifests[0].digest""",
out = name + ".json.sha256", # path chosen to match rules_docker for easy migration
**kwargs
)
119 changes: 64 additions & 55 deletions private/oci/sign_and_push.bzl
Original file line number Diff line number Diff line change
@@ -1,47 +1,59 @@
"rules for signing, attesting and pushing images"

load("@bazel_skylib//rules:write_file.bzl", "write_file")
load("@rules_oci//cosign:defs.bzl", "cosign_attest", "cosign_sign")
load("@rules_oci//oci:defs.bzl", "oci_push")
load("//private/pkg:oci_image_spdx.bzl", "oci_image_spdx")
load(":digest.bzl", "digest")

PUSH_AND_SIGN_CMD = """\
# Push {IMAGE}
repository="$(stamp "{REPOSITORY}")"
tag="$(stamp "{TAG}")"
[[ -n $EXPORT ]] && echo "$repository:$tag" >> $EXPORT
# Push the image by its digest
"$(realpath {PUSH_CMD})" --repository "$repository"
# Attest the sbom
GOOGLE_SERVICE_ACCOUNT_NAME="$KEYLESS" "$(realpath {ATTEST_CMD})" --repository "$repository" --yes
# Sign keyless by using an identity
GOOGLE_SERVICE_ACCOUNT_NAME="$KEYLESS" "$(realpath {SIGN_CMD})" --repository "$repository" --yes
digest="$(cat {DIGEST})"
echo "Pushing $repository@$digest"
{CRANE} push {IMAGE} "$repository@$digest"
{COSIGN} attest "$repository@$digest" --predicate "{SBOM}" --type "spdx" --yes
{COSIGN} sign "$repository@$digest" --yes
"""

# Tag the image
"$(realpath {PUSH_CMD})" --repository "$repository" --tag "$tag"
TAG_CMD = """\
# Tag {IMAGE}
from="$(stamp "{FROM}")"
to="$(stamp "{TO}")"
{CRANE} copy "$from" "$to"
"""

def _sign_and_push_impl(ctx):
cmds = []
runfiles = ctx.runfiles(files = ctx.files.targets + [ctx.version_file])

for (target, url) in ctx.attr.targets.items():
runfiles = ctx.runfiles(files = ctx.files.targets + [ctx.version_file, ctx.file._crane, ctx.file._cosign])

for (image, target) in ctx.attr.targets.items():
files = target[DefaultInfo].files.to_list()
runfiles = runfiles.merge(target[DefaultInfo].default_runfiles)
repository_and_tag = url.split(":")

all_refs = ctx.attr.refs[image]
first_ref = all_refs[0]
repository_and_tag = first_ref.split(":")
cmds.append(
PUSH_AND_SIGN_CMD.format(
ATTEST_CMD = files[0].short_path,
SIGN_CMD = files[1].short_path,
PUSH_CMD = files[2].short_path,
IMAGE = files[0].short_path,
SBOM = files[1].short_path,
DIGEST = files[2].short_path,
CRANE = ctx.file._crane.short_path,
COSIGN = ctx.file._cosign.short_path,
REPOSITORY = repository_and_tag[0],
TAG = repository_and_tag[1],
),
)

for ref in all_refs[1:]:
cmds.append(
TAG_CMD.format(
IMAGE = image,
FROM = first_ref,
TO = ref,
CRANE = ctx.file._crane.short_path,
),
)

executable = ctx.actions.declare_file("{}_sign_and_push.sh".format(ctx.label.name))
ctx.actions.expand_template(
template = ctx.file._push_tpl,
Expand All @@ -58,8 +70,11 @@ def _sign_and_push_impl(ctx):
sign_and_push = rule(
implementation = _sign_and_push_impl,
attrs = {
"targets": attr.label_keyed_string_dict(mandatory = True, cfg = "exec"),
"refs": attr.string_list_dict(mandatory = True),
"targets": attr.string_keyed_label_dict(mandatory = True, cfg = "exec"),
"_push_tpl": attr.label(default = "sign_and_push.sh.tpl", allow_single_file = True),
"_crane": attr.label(allow_single_file = True, cfg = "exec", default = "@oci_crane_toolchains//:current_toolchain"),
"_cosign": attr.label(allow_single_file = True, cfg = "exec", default = "@oci_cosign_toolchains//:current_toolchain"),
},
executable = True,
)
Expand All @@ -69,59 +84,53 @@ def sign_and_push_all(name, images):
Args:
name: name of the target
images: a dict where keys are fully qualified image url and values are image labels
images: a dict where keys are fully qualified image reference and values are image label
"""
image_dict = dict()
query_dict = dict()
for (idx, (url, image)) in enumerate(images.items()):
oci_push(
name = "{}_{}_push".format(name, idx),
image = image,
repository = "repository.default.local",
)

dedup_image_dict = dict()
dedup_push_dict = dict()

for (idx, (ref, image)) in enumerate(images.items()):
if image in dedup_image_dict:
dedup_image_dict[image].append(ref)
else:
dedup_image_dict[image] = [ref]

for (idx, (image, ref)) in enumerate(dedup_image_dict.items()):
oci_image_spdx(
name = "{}_{}_sbom".format(name, idx),
image = image,
)
cosign_attest(
name = "{}_{}_attest".format(name, idx),
image = image,
type = "spdx",
predicate = "{}_{}_sbom".format(name, idx),
repository = "repository.default.local",
)
cosign_sign(
name = "{}_{}_sign".format(name, idx),
digest(
name = "{}_{}_digest".format(name, idx),
image = image,
repository = "repository.default.local",
)

native.filegroup(
name = "{}_{}".format(name, idx),
srcs = [
":{}_{}_attest".format(name, idx),
":{}_{}_sign".format(name, idx),
":{}_{}_push".format(name, idx),
image,
":{}_{}_sbom".format(name, idx),
":{}_{}_digest".format(name, idx),
],
)

image_dict[":{}_{}".format(name, idx)] = url
query_dict[image] = url.split(":") + [":{}_{}_push".format(name, idx)]
dedup_push_dict[image] = "{}_{}".format(name, idx)

write_file(
name = name + ".query",
content = [
"{repo} {tag} {push_label} {image_label}".format(
repo = ref[0],
tag = ref[1],
push_label = ref[2],
image_label = image,
"{repo} {image}".format(
repo = refs[0],
image = image,
)
for (image, ref) in query_dict.items()
for (image, refs) in dedup_image_dict.items()
],
out = name + "_query",
)

sign_and_push(
name = name,
targets = image_dict,
targets = dedup_push_dict,
refs = dedup_image_dict,
)
8 changes: 2 additions & 6 deletions private/oci/sign_and_push.sh.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,13 @@
set -o pipefail -o errexit -o nounset

KEYLESS="${KEYLESS:-}"
EXPORT=""

while (( $# > 0 )); do
case $1 in
(--keyless)
KEYLESS="$2"
shift
shift;;
(--export)
EXPORT="$2"
echo -n "" > $EXPORT
shift
shift;;
(*)
echo "unknown arg $1"
exit 1
Expand Down Expand Up @@ -42,6 +36,8 @@ function stamp() {
}


export GOOGLE_SERVICE_ACCOUNT_NAME="${KEYLESS}"

{{CMDS}}

echo ""
Expand Down
18 changes: 9 additions & 9 deletions private/tools/diff.bash
Original file line number Diff line number Diff line change
Expand Up @@ -176,39 +176,39 @@ stamp_origin() {
}

function test_image() {
IFS=" " read -r repo tag push_label image_label <<<"$1"
IFS=" " read -r repo image_label <<<"$1"

if [[ "${ONLY}" != "" && "${ONLY}" != "$image_label" ]]; then
return
fi

repo_origin=$(stamp_origin "$repo")
repo_stage=$(stamp_stage "$repo")
tag_stamped=$(stamp_origin "$tag")

if [[ "${SKIP_INDEX}" == "1" ]]; then
if ! crane manifest "$repo_origin:$tag_stamped" | jq -e '.mediaType == "application/vnd.oci.image.manifest.v1+json"' > /dev/null; then
echo "⏭️ Skipping image index $repo_origin:$tag_stamped "
if ! crane manifest "$repo_origin" | jq -e '.mediaType == "application/vnd.oci.image.manifest.v1+json"' > /dev/null; then
echo "⏭️ Skipping image index $repo_origin"
return
fi
fi

echo ""
echo "🚧 Diffing $repo_origin:$tag_stamped against $repo_stage:$tag_stamped"
echo "🚧 Diffing $repo_origin against $repo_stage"
echo ""

bazel run $push_label -- --repository $repo_stage --tag $tag_stamped
if ! diffoci diff --pull=always --all-platforms --semantic "$repo_origin:$tag_stamped" "$repo_stage:$tag_stamped"; then
bazel build "$image_label"
crane push "$(bazel cquery --output=files $image_label)" "$repo_stage"
if ! diffoci diff --pull=always --all-platforms --semantic "$repo_origin" "$repo_stage"; then
echo ""
echo " 🔬 To reproduce: bazel run //private/tools:diff -- --only $image_label"
echo ""
echo "👎 $repo_origin:$tag_stamped and $repo_stage:$tag_stamped are different."
echo "👎 $repo_origin and $repo_stage are different."
if [[ "${SET_GITHUB_OUTPUT}" == "1" ]]; then
echo "$image_label" >> "$CHANGED_IMAGES_FILE"
fi
else
echo ""
echo "👍 $repo_origin:$tag_stamped and $repo_stage:$tag_stamped are identical."
echo "👍 $repo_origin and $repo_stage are identical."
fi
}

Expand Down

0 comments on commit 2535a58

Please sign in to comment.