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

Extract container image hash for use during later build steps #60

Closed
malt3 opened this issue Jan 30, 2023 · 10 comments · Fixed by #346
Closed

Extract container image hash for use during later build steps #60

malt3 opened this issue Jan 30, 2023 · 10 comments · Fixed by #346
Labels
documentation Improvements or additions to documentation good first issue Good for newcomers

Comments

@malt3
Copy link
Contributor

malt3 commented Jan 30, 2023

I would like to be able to create container image locally and then use the container image hash during a later build step (to embed the container image hash in a binary).
Is this already supported or do you have a pointer to how I could contribute this functionality?
Kind regards

@thesayyn
Copy link
Collaborator

thesayyn commented Jan 30, 2023

this can be done today using a tool like jq. oci_image rule outputs a directory that has an index.json file which contains the sha256 sum of the image.

an example using a genrule and bazel-lib/jq

genrule(
    name = "generate",
    srcs = [":image"],
    outs = ["sha256.sum"],
    cmd = "$(JQ_BIN) -r '.manifests[0].digest' $(location :image)/index.json > $@",
    toolchains = ["@jq_toolchains//:resolved_toolchain"],
)

index.json is part of opencontainers spec. see here . other useful bits of information can be gathered from the said file.

@thesayyn thesayyn added the documentation Improvements or additions to documentation label Jan 31, 2023
@malt3
Copy link
Contributor Author

malt3 commented Jan 31, 2023

Thanks! Super helpful

@malt3 malt3 closed this as completed Jan 31, 2023
@thesayyn
Copy link
Collaborator

thesayyn commented Mar 7, 2023

going to reopen. we should add an example for this

@aran
Copy link

aran commented Aug 11, 2023

Just wrote my own hacky version and then found this afterward. Would be great to have a good way to do this documented. genrule can't be used in a reusable .bzl right? rules_docker has a built-in '.digest' on container_push that serves this purpose. Convenient when building a kubernetes resource that intends to refer to an image by precise sha256.

@farcop
Copy link

farcop commented Aug 11, 2023

@aran Could you show what you got?

@aran
Copy link

aran commented Aug 11, 2023

@farcop sure.

(much credit to chatgpt... and I am not going to be using this)

load("@aspect_bazel_lib//lib:yq.bzl", "yq")

# Extract the index.json from the OCI image
def _extract_index_json_impl(ctx):
    output = ctx.outputs.out
    ctx.actions.run_shell(
        inputs = [ctx.files.src[0]],
        outputs = [output],
        command = """
readonly LOCATION={src}/index.json
cat $LOCATION > {out}
        """.format(src=ctx.files.src[0].path, out=output.path)
    )

extract_index_json = rule(
    implementation = _extract_index_json_impl,
    attrs = {
        "src": attr.label(mandatory=True, allow_single_file=True),
        "out": attr.output(mandatory=True),
    },
)

def _local_repository_patch_impl(ctx):
    output = ctx.outputs.out
    ctx.actions.run_shell(
        inputs = [ctx.files.template[0], ctx.files.digest[0]],
        outputs = [output],
        command = """
readonly LOCATION={digest}
sed -e s/DIGEST/$(cat $LOCATION)/ {template} > {out}
        """.format(digest=ctx.files.digest[0].path, template=ctx.files.template[0].path, out=output.path)
    )

local_repository_patch = rule(
    implementation = _local_repository_patch_impl,
    attrs = {
        "template": attr.label(mandatory=True, allow_single_file=True),
        "digest": attr.label(mandatory=True, allow_single_file=True),
        "out": attr.output(mandatory=True),
    },
)

# Creates a target called 'name'
# 'name' is 'patch_template_target' with the literal string "DIGEST" replaced by the digest of 'src_image'.
def oci_digest_patch(
        name,
        src_image,
        patch_template_target,
        visibility):

    extract_index_json(
        name = name + "_index_json",
        src = src_image,
        out = name + "_index.json",
    )

    # Extract the digest using yq
    yq(
        name = name + "_digest",
        srcs = [name + "_index_json"],
        outs = [name + "_digest.txt"],
        expression = ".manifests[0].digest",
    )

    local_repository_patch(
        name = name,
        template = patch_template_target,
        digest = name + "_digest.txt",
        out = name + "_local-repository-patch.yaml",
        visibility = visibility,
    )

@alexeagle
Copy link
Collaborator

@juanique points out that the digest we report here doesn't match what you get from docker inspect my_image:latest | grep Id, but the equivalent feature from rules_docker does. Investigating.

@alexeagle alexeagle reopened this Dec 14, 2023
@alexeagle
Copy link
Collaborator

Testing in this repo,

% bazel build examples/push:image.digest && cat bazel-bin/examples/push/image.json.sha256
Target //examples/push:image.digest up-to-date:
  bazel-bin/examples/push/image.json.sha256
sha256:64d822c5d1595a8c8c45b17cf7ef4f15745725a627239ab843aefbafa6b3f2f6

# Change "<ORG>" to $USER in the BUILD file, then,
% bazel run --stamp examples/push:push_image_index
2023/12/14 12:10:20 pushed blob: sha256:02929d0275250b923d01d4be6571200abdadf0faf13aefb3b643264b685e2706
2023/12/14 12:10:21 index.docker.io/alexeagle/image@sha256:64d822c5d1595a8c8c45b17cf7ef4f15745725a627239ab843aefbafa6b3f2f6: digest: sha256:64d822c5d1595a8c8c45b17cf7ef4f15745725a627239ab843aefbafa6b3f2f6 size: 248
2023/12/14 12:10:21 index.docker.io/alexeagle/image@sha256:b12b0c63a611bc15c448191b4a5475ee4cc3ed5b5d3acabc30dad0701c7b9096: digest: sha256:b12b0c63a611bc15c448191b4a5475ee4cc3ed5b5d3acabc30dad0701c7b9096 size: 375
2023/12/14 12:10:22 existing manifest: sha256:64d822c5d1595a8c8c45b17cf7ef4f15745725a627239ab843aefbafa6b3f2f6
2023/12/14 12:10:23 index.docker.io/alexeagle/image:nightly: digest: sha256:b12b0c63a611bc15c448191b4a5475ee4cc3ed5b5d3acabc30dad0701c7b9096 size: 375

pushes to dockerhub, and their site confirms the digest matches:
https://hub.docker.com/layers/alexeagle/image/nightly/images/sha256-64d822c5d1595a8c8c45b17cf7ef4f15745725a627239ab843aefbafa6b3f2f6?context=repo

So, the digest for the oci_image does match what ends up on the registry from oci_push.

However, oci_tarball produces a different result. Adding a trivial one to examples/push/BUILD.bazel like

oci_tarball(
    name = "tarball",
    image = ":image",
    repo_tags = ["my_image:latest"],
)

I can reproduce that it doesn't load something into the docker daemon that looks the same as if the image were pushed:

alexeagle@system76-pc:~/Projects/rules_oci$ bazel run examples/push:tarball
INFO: Analyzed target //examples/push:tarball (1 packages loaded, 8 targets configured).
INFO: Found 1 target...
Target //examples/push:tarball up-to-date:
  bazel-bin/examples/push/tarball/tarball.tar
INFO: Elapsed time: 0.133s, Critical Path: 0.07s
INFO: 7 processes: 6 internal, 1 linux-sandbox.
INFO: Running command line: bazel-bin/examples/push/tarball.sh
INFO: Build Event Protocol files produced successfully.
INFO: Build completed successfully, 7 total actions
Loaded image: my_image:latest
alexeagle@system76-pc:~/Projects/rules_oci$ docker inspect my_image:latest | grep sha
        "Id": "sha256:02929d0275250b923d01d4be6571200abdadf0faf13aefb3b643264b685e2706",```

I think this is by design, in the sense that https://github.com/bazel-contrib/rules_oci/blob/main/oci/private/tarball.sh.tpl doesn't call crane so it doesn't guarantee to create the same image digest, but @thesayyn would know for sure.

@thesayyn
Copy link
Collaborator

I believe this is different issue than docker tarballs not matching the hash in oci-layout.

@thesayyn
Copy link
Collaborator

thesayyn commented Jun 8, 2024

Coming back to this again, unfortunately the conversion from oci_image to oci_tarball is not full fidelity conversion because how docker tarballs work. Even though images semantically are the same, they don't necessarily have the same hash.

Similar feature request could be that oci_tarball creates a sub target that would give you the imageid that docker daemon would report.

I am going to file a separate issue for this for better tracking.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation good first issue Good for newcomers
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants