diff --git a/examples/push/BUILD.bazel b/examples/push/BUILD.bazel index ab2814b9..dfa5e95a 100644 --- a/examples/push/BUILD.bazel +++ b/examples/push/BUILD.bazel @@ -11,7 +11,7 @@ oci_image( oci_push( name = "push_image", image = ":image", - repository = "index.docker.io//image", + repository = "index.docker.io:9899//image", repotags = ["latest"], ) diff --git a/oci/BUILD.bazel b/oci/BUILD.bazel index ed8985fc..2cc08a4b 100644 --- a/oci/BUILD.bazel +++ b/oci/BUILD.bazel @@ -37,6 +37,7 @@ bzl_library( visibility = ["//visibility:public"], deps = [ "//oci/private:pull", + "//oci/private:util", ], ) diff --git a/oci/private/BUILD.bazel b/oci/private/BUILD.bazel index f4027c28..77f8dcb1 100644 --- a/oci/private/BUILD.bazel +++ b/oci/private/BUILD.bazel @@ -63,6 +63,9 @@ bzl_library( "//docs:__pkg__", "//oci:__subpackages__", ], + deps = [ + "//oci/private:util", + ], ) bzl_library( diff --git a/oci/private/pull.bzl b/oci/private/pull.bzl index 2dee9447..fbbe4ec0 100644 --- a/oci/private/pull.bzl +++ b/oci/private/pull.bzl @@ -51,43 +51,6 @@ _WWW_AUTH = { }, } -def _parse_image(image): - """Support syntax sugar in oci_pull where multiple data fields are in a single string, "image" - - """ - - scheme = "https" - digest = None - tag = None - - if image.startswith("http://"): - image = image[len("http://"):] - scheme = "http" - if image.startswith("https://"): - image = image[len("https://"):] - - # Check syntax sugar for digest/tag suffix on image - if image.rfind("@") > 0: - image, digest = image.rsplit("@", 1) - - # Check if the last colon has no slashes after it. - # Matches debian:latest and myregistry:8000/myimage:latest - # but does not match myregistry:8000/myimage - colon = image.rfind(":") - if colon > 0 and image[colon:].find("/") == -1: - image, tag = image.rsplit(":", 1) - - # Syntax sugar, special case for dockerhub - if image.startswith("docker.io/"): - image = "index." + image - - # If image has no repository, like bare "ubuntu" we assume it's dockerhub - if image.find("/") == -1: - image = "index.docker.io/library/" + image - registry, repository = image.split("/", 1) - - return (scheme, registry, repository, digest, tag) - def _strip_host(url): # TODO: a principled way of doing this return url.replace("http://", "").replace("https://", "").replace("/v1/", "") @@ -552,7 +515,3 @@ oci_alias = repository_rule( }, ), ) - -lib = struct( - parse_image = _parse_image, -) diff --git a/oci/private/push.bzl b/oci/private/push.bzl index ee126fe7..bf35dee3 100644 --- a/oci/private/push.bzl +++ b/oci/private/push.bzl @@ -1,5 +1,7 @@ "Implementation details for the push rule" +load("//oci/private:util.bzl", "util") + _DOC = """Push an oci_image or oci_image_index to a remote registry. Internal rule used by the [oci_push macro](/docs/push.md#oci_push). @@ -101,8 +103,9 @@ def _impl(ctx): if not ctx.file.image.is_directory: fail("image attribute must be a oci_image or oci_image_index") - if ctx.attr.repository.find(":") != -1 or ctx.attr.repository.find("@") != -1: - fail("repository attribute should not contain digest or tag.") + _, _, _, maybe_digest, maybe_tag = util.parse_image(ctx.attr.repository) + if maybe_digest or maybe_tag: + fail("`repository` attribute should not contain digest or tag. got: {}".format(ctx.attr.repository)) executable = ctx.actions.declare_file("push_%s.sh" % ctx.label.name) files = [ctx.file.image] diff --git a/oci/private/util.bzl b/oci/private/util.bzl index ed5f7b90..7e5b5941 100644 --- a/oci/private/util.bzl +++ b/oci/private/util.bzl @@ -1,5 +1,46 @@ """Utilities""" +def _parse_image(image): + """Support syntax sugar in oci_pull where multiple data fields are in a single string, "image" + + Args: + image: full-qualified reference url + Returns: + a tuple containing scheme, registry, repository, digest, and tag information. + """ + + scheme = "https" + digest = None + tag = None + + if image.startswith("http://"): + image = image[len("http://"):] + scheme = "http" + if image.startswith("https://"): + image = image[len("https://"):] + + # Check syntax sugar for digest/tag suffix on image + if image.rfind("@") > 0: + image, digest = image.rsplit("@", 1) + + # Check if the last colon has no slashes after it. + # Matches debian:latest and myregistry:8000/myimage:latest + # but does not match myregistry:8000/myimage + colon = image.rfind(":") + if colon > 0 and image[colon:].find("/") == -1: + image, tag = image.rsplit(":", 1) + + # Syntax sugar, special case for dockerhub + if image.startswith("docker.io/"): + image = "index." + image + + # If image has no repository, like bare "ubuntu" we assume it's dockerhub + if image.find("/") == -1: + image = "index.docker.io/library/" + image + registry, repository = image.split("/", 1) + + return (scheme, registry, repository, digest, tag) + def sha256(rctx, path): """Returns hashsum of file at path @@ -21,3 +62,7 @@ def warning(rctx, message): "echo", "\033[0;33mWARNING:\033[0m {}".format(message), ], quiet = False) + +util = struct( + parse_image = _parse_image, +) diff --git a/oci/pull.bzl b/oci/pull.bzl index d1565060..fe9bc0f5 100644 --- a/oci/pull.bzl +++ b/oci/pull.bzl @@ -36,7 +36,8 @@ oci_image( ``` """ -load("//oci/private:pull.bzl", "lib", "oci_alias", _oci_pull = "oci_pull") +load("//oci/private:pull.bzl", "oci_alias", _oci_pull = "oci_pull") +load("//oci/private:util.bzl", "util") # Note: there is no exhaustive list, image authors can use whatever name they like. # This is only used for the oci_alias rule that makes a select() - if a mapping is missing, @@ -93,7 +94,7 @@ def oci_pull(name, image = None, repository = None, registry = None, platforms = fail("One of 'image' or '{registry, repository}' must be set") if image: - scheme, registry, repository, maybe_digest, maybe_tag = lib.parse_image(image) + scheme, registry, repository, maybe_digest, maybe_tag = util.parse_image(image) if maybe_digest: digest = maybe_digest if maybe_tag: diff --git a/oci/tests/pull_tests.bzl b/oci/tests/pull_tests.bzl index 9e9781f1..6d626bb3 100644 --- a/oci/tests/pull_tests.bzl +++ b/oci/tests/pull_tests.bzl @@ -1,39 +1,39 @@ "Unit tests for oci_pull implementation" load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") -load("//oci/private:pull.bzl", "lib") +load("//oci/private:util.bzl", "util") def _parse_image_test_impl(ctx): env = unittest.begin(ctx) asserts.equals( env, ("https", "index.docker.io", "library/debian", None, None), - lib.parse_image("debian"), + util.parse_image("debian"), ) asserts.equals( env, ("https", "index.docker.io", "library/debian", None, None), - lib.parse_image("docker.io/library/debian"), + util.parse_image("docker.io/library/debian"), ) asserts.equals( env, ("https", "index.docker.io", "library/debian", None, "latest"), - lib.parse_image("debian:latest"), + util.parse_image("debian:latest"), ) asserts.equals( env, ("https", "index.docker.io", "library/debian", "sha256:deadbeef", None), - lib.parse_image("debian@sha256:deadbeef"), + util.parse_image("debian@sha256:deadbeef"), ) asserts.equals( env, ("https", "index.docker.io", "library/debian", None, None), - lib.parse_image("https://docker.io/library/debian"), + util.parse_image("https://docker.io/library/debian"), ) asserts.equals( env, ("http", "localhost:8080", "some/image", None, "stable"), - lib.parse_image("http://localhost:8080/some/image:stable"), + util.parse_image("http://localhost:8080/some/image:stable"), ) return unittest.end(env)