...

Text file src/edge-infra.dev/hack/build/rules/container/push.bzl

Documentation: edge-infra.dev/hack/build/rules/container

     1"""Defines a rule for wrapping container pushing rules from upstream providers (e.g., rules_oci).
     2
     3The wrapping rule allows consistent configuration of destination repositories
     4and makes information about the destination repository available to downstream
     5rules via the OCIPushInfo provider.
     6"""
     7
     8load("@aspect_bazel_lib//lib:utils.bzl", "propagate_common_rule_attributes")
     9load("@rules_oci//oci:defs.bzl", _rules_oci_push = "oci_push")
    10load("@io_bazel_rules_docker//container:container.bzl", _rules_docker_push = "container_push")
    11load(":constants.bzl", "COMMON_TAGS")
    12load("//hack/build/rules/container/sign:container_sign.bzl", "container_sign")
    13load(":push_info.bzl", "OCIPushInfo")
    14
    15def _impl(ctx):
    16    if ctx.attr.rules_docker_push and ctx.attr.rules_oci_push:
    17        fail("only one of 'rules_docker_push' and 'rules_oci_push' can be specified")
    18
    19    digest = ctx.file.digest
    20
    21    # Create ref by combining the repository and digest
    22    ref = ctx.actions.declare_file("{0}.ref".format(ctx.label.name))
    23    ctx.actions.run_shell(
    24        outputs = [ref],
    25        inputs = [ctx.file.repository_file, digest],
    26        command = "echo \"$(cat {repo})@$(cat {digest})\" > {ref}".format(
    27            repo = ctx.file.repository_file.path,
    28            digest = digest.path,
    29            ref = ref.path,
    30        ),
    31    )
    32
    33    # Resolve the pusher binary we will be wrapping and the required runfiles
    34    # that will be forwarded by this rule.
    35    wrapped_pusher = None
    36    runfiles = None
    37
    38    # Create pusher binary which simply invokes the wrapped pusher. This is
    39    # needed because Bazel won't allow us to provide another rule's executable
    40    # as our own.
    41    pusher = ctx.actions.declare_file(ctx.label.name)
    42
    43    if ctx.attr.rules_docker_push != None:
    44        wrapped_pusher = ctx.executable.rules_docker_push
    45        runfiles = ctx.runfiles(files = [ctx.file.repository_file])
    46        runfiles = runfiles.merge(ctx.attr.rules_docker_push[DefaultInfo].default_runfiles)
    47
    48        # rules_docker needs a special wrapper to ensure that we can update
    49        # the hardcoded "registry" field.
    50        # Use a hardcoded tag because I'm tired of rules_docker and pusha/leaf
    51        # will handle tagging post-push for us.
    52        ctx.actions.write(
    53            output = pusher,
    54            content = """#!/usr/bin/env bash
    55{pusher} --dst=$(cat {repo}):dev "$@"
    56""".format(
    57                pusher = wrapped_pusher.short_path,
    58                repo = ctx.file.repository_file.short_path,
    59            ),
    60            is_executable = True,
    61        )
    62    else:
    63        wrapped_pusher = ctx.executable.rules_oci_push
    64        runfiles = ctx.attr.rules_oci_push[DefaultInfo].default_runfiles
    65        ctx.actions.write(
    66            output = pusher,
    67            content = """#!/usr/bin/env bash
    68{pusher} "$@"
    69""".format(
    70                pusher = wrapped_pusher.short_path,
    71            ),
    72            is_executable = True,
    73        )
    74
    75    return [
    76        DefaultInfo(
    77            files = depset([ctx.file.repository_file, ref, digest]),
    78            executable = pusher,
    79            runfiles = runfiles,
    80        ),
    81        OCIPushInfo(
    82            repo = ctx.file.repository_file,
    83            digest = digest,
    84            ref = ref,
    85            # Have to provide our pusher if rules_docker, due to registry override
    86            pusher = wrapped_pusher if ctx.attr.rules_docker_push == None else pusher,
    87            runfiles = runfiles,
    88        ),
    89    ]
    90
    91# container_push rule wrapper definition. Consumers interact with the rule via
    92# the public macro
    93_container_push2 = rule(
    94    doc = """container_push wraps upstream container pushing rules to provide a
    95consistent surface area for controlling the destination repository and accessing
    96that information from rules that depend on it.
    97""",
    98    implementation = _impl,
    99    toolchains = [
   100        "@rules_oci//cosign:toolchain_type",
   101    ],
   102    attrs = {
   103        "from_third_party": attr.bool(
   104            doc = "Flag to signify if container push is pushing a third party image",
   105            default = False,
   106            mandatory = False,
   107        ),
   108        "repository_file": attr.label(
   109            doc = "File containing full OCI repository to push container to",
   110            allow_single_file = True,
   111            mandatory = True,
   112        ),
   113        "digest": attr.label(
   114            doc = "File containing digest for the container to push.",
   115            allow_single_file = True,
   116            mandatory = False,
   117        ),
   118        # TODO: once rules_docker is dropped entirely, these attrs can be condensed
   119        # into a single "wrapped_push" attr, or we can use Bazel 7 rule extension
   120        # APIs.
   121        "rules_docker_push": attr.label(
   122            doc = "rules_docker container_push target. Mutually exclusive with rules_oci_push.",
   123            executable = True,
   124            cfg = "exec",
   125        ),
   126        "rules_oci_push": attr.label(
   127            doc = "rules_oci container_push target. Mutually exclusive with rules_docker_push.",
   128            executable = True,
   129            cfg = "target",
   130        ),
   131    },
   132    executable = True,
   133)
   134
   135def container_push2(
   136        image,
   137        image_name,
   138        repository_file,
   139        digest = None,
   140        name = "container_push",
   141        rules_docker = False,
   142        tags = [],
   143        tag = None,
   144        from_third_party = False,
   145        **kwargs):
   146    """Creates standardized container_push rule for consistent registry config.
   147
   148    It instantiates a pushing rule that is wrapped by our own implementation,
   149    so that we can forward the pushing binary and all of the information
   150    required to push it or replace container references in rules that depend
   151    on this.
   152
   153    Args:
   154        name:            name of the returned container_push target
   155        image:           image target to push i.e. @ubuntu
   156        image_name:      image name i.e. library/ubuntu
   157        repository_file: label to a file containing an OCI repository, which is
   158                         combined with the 'image_name' to create the full OCI
   159                         repository string
   160        digest:          digest of the image if set explicitly
   161        tags:            list of tags to add to Bazel target
   162        tag:             image tag, defaults to --define value image-tag
   163        from_third_party:
   164                         a boolean flag used to signify if this container push
   165                         is generated by a third party pull image. Used to distinguish
   166                         which container pushes should be run when publishing all
   167                         third party sourced container images.
   168        rules_docker:    whether or not this container_push target will be using
   169                         rules_docker or not
   170        **kwargs: Additional key/value pairs to pass to produced targets
   171    """
   172
   173    forwarded_args = propagate_common_rule_attributes(kwargs)
   174
   175    # Generate full repository string from build config setting and image name.
   176    full_repository_file = "{0}.repo".format(name)
   177    native.genrule(
   178        name = "{0}_repo".format(name),
   179        srcs = [
   180            repository_file,
   181        ],
   182        outs = [full_repository_file],
   183        cmd = "echo \"$$(cat $(SRCS))/{image}\" > $@".format(image = image_name),
   184        **forwarded_args
   185    )
   186
   187    if rules_docker:
   188        # Massage repo file into what rules_docker expects: everything after
   189        # the registry URL.
   190        rules_docker_repo_file = "_{0}_rules_docker.repo".format(name)
   191        native.genrule(
   192            name = "_{0}_rules_docker_repo".format(name),
   193            srcs = [
   194                repository_file,
   195            ],
   196            outs = [rules_docker_repo_file],
   197            cmd = "echo \"$$(cat $(SRCS) | cut -d'/' -f 2-)/{image}\" > $@".format(image = image_name),
   198        )
   199
   200        rules_docker_push_name = "_rules_docker_{0}".format(name)
   201        _rules_docker_push(
   202            name = rules_docker_push_name,
   203            format = "Docker",
   204            image = image,
   205            tags = COMMON_TAGS + tags,
   206            # The registry value will be overriden from the repositories file
   207            # in our container_push rule
   208            registry = "us-east1-docker.pkg.dev",
   209            # Will be replaced by repository_file but still a mandatory attr :facepalm:
   210            repository = "placeholder",
   211            repository_file = rules_docker_repo_file,
   212            tag = tag if tag != None else "dev",
   213            stamp = "@io_bazel_rules_docker//stamp:never",
   214            **forwarded_args
   215        )
   216
   217        _container_push2(
   218            name = name,
   219            rules_docker_push = ":_rules_docker_" + name,
   220            digest = digest or rules_docker_push_name + ".digest",
   221            repository_file = full_repository_file,
   222            tags = tags,
   223            **forwarded_args
   224        )
   225
   226        return
   227
   228    rules_oci_push_name = "_rules_oci_{0}".format(name)
   229    _rules_oci_push(
   230        remote_tags = [tag] if tag != None else ["dev"],
   231        name = rules_oci_push_name,
   232        image = image,
   233        repository_file = full_repository_file,
   234        **forwarded_args
   235    )
   236
   237    _container_push2(
   238        name = name,
   239        rules_oci_push = ":" + rules_oci_push_name,
   240        digest = digest or image + ".digest",
   241        repository_file = full_repository_file,
   242        tags = tags,
   243        from_third_party = from_third_party if from_third_party else None,
   244        **forwarded_args
   245    )
   246
   247    container_sign(
   248        name = name.replace("push", "sign"),
   249        container_push = ":{0}".format(name),
   250        **forwarded_args
   251    )

View as plain text