From f34602d2c29a01da3f5653a9d3e931cbf94c784d Mon Sep 17 00:00:00 2001 From: Brian Myers Date: Thu, 2 Jan 2025 13:15:22 -0600 Subject: [PATCH] replace oci_pull with pure starlark --- .github/workflows/main.yaml | 2 - BUILD.bazel | 24 +- MODULE.bazel | 8 +- README.md | 18 +- bin/BUILD.bazel | 16 - docs/defs.md | 82 ++- docs/providers.md | 57 -- docs/repositories.md | 19 +- examples/go-multiarch-image/BUILD.bazel | 75 ++- go.mod | 6 +- go.sum | 67 --- go/cmd/ocitool/BUILD.bazel | 6 - go/cmd/ocitool/gen_cmd.go | 88 ---- go/cmd/ocitool/main.go | 21 - go/cmd/ocitool/pull_cmd.go | 78 --- go/pkg/ociutil/BUILD.bazel | 2 - go/pkg/ociutil/bazel.go | 199 ------- go/pkg/ociutil/fs.go | 6 + oci/BUILD.bazel | 46 +- oci/defs.bzl | 4 + oci/private/BUILD.bazel | 2 + oci/private/oci_image.bzl | 11 +- oci/private/oci_image_index.bzl | 11 +- oci/private/oci_image_layer.bzl | 11 +- oci/private/oci_image_layout.bzl | 113 ++-- oci/private/oci_push.bzl | 21 +- oci/private/repositories/authn.bzl | 352 +++++++++++++ oci/private/repositories/download.bzl | 146 ++++++ oci/private/repositories/oci_blob.bzl | 56 -- .../repositories/oci_image_index_manifest.bzl | 30 -- .../repositories/oci_image_manifest.bzl | 36 -- oci/private/repositories/oci_layout_index.bzl | 45 -- oci/private/repositories/oci_pull.bzl | 487 ++++++++++++++---- oci/private/repositories/oci_pulled_image.bzl | 125 +++++ oci/providers.bzl | 48 +- oci/toolchain.bzl | 113 +--- release/BUILD.bazel | 10 - release/README.md | 4 +- release/pkg_go_binary_multiple_platforms.bzl | 59 --- 39 files changed, 1316 insertions(+), 1188 deletions(-) delete mode 100755 bin/BUILD.bazel delete mode 100644 go/cmd/ocitool/gen_cmd.go delete mode 100644 go/cmd/ocitool/pull_cmd.go delete mode 100644 go/pkg/ociutil/bazel.go create mode 100644 oci/private/repositories/authn.bzl create mode 100644 oci/private/repositories/download.bzl delete mode 100644 oci/private/repositories/oci_blob.bzl delete mode 100644 oci/private/repositories/oci_image_index_manifest.bzl delete mode 100644 oci/private/repositories/oci_image_manifest.bzl delete mode 100644 oci/private/repositories/oci_layout_index.bzl create mode 100644 oci/private/repositories/oci_pulled_image.bzl delete mode 100644 release/pkg_go_binary_multiple_platforms.bzl diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 9c1b051..a18e8dd 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -16,7 +16,5 @@ jobs: - run: mkdir -p ~/.local/bin - run: echo -e "#!/usr/bin/env bash\n echo '{\"ServerURL\":\"ghcr.io\",\"Username\":\"Bearer\",\"Secret\":\"${{ secrets.GITHUB_TOKEN }}\"}'" > ~/.local/bin/docker-credential-ghcr - run: chmod +x ~/.local/bin/docker-credential-ghcr - # Setup local toolchain - - run: bazel build --config=ci //go/cmd/ocitool:ocitool && cp bazel-bin/go/cmd/ocitool/ocitool_/ocitool bin/ocitool-linux-amd64 # Run all tests - run: bazel test --config=ci //... diff --git a/BUILD.bazel b/BUILD.bazel index 28c5c5e..3fbba4a 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,7 +1,5 @@ -load("@aspect_bazel_lib//lib:write_source_files.bzl", "write_source_files") load("@gazelle//:def.bzl", "DEFAULT_LANGUAGES", "gazelle", "gazelle_binary") load("@npm//:defs.bzl", "npm_link_all_packages") -load("//oci:toolchain.bzl", "oci_local_toolchain") # gazelle:prefix github.com/DataDog/rules_oci # gazelle:go_naming_convention go_default_library @@ -11,8 +9,9 @@ npm_link_all_packages( name = "node_modules", ) -oci_local_toolchain( - name = "oci_local_toolchain", +alias( + name = "format", + actual = "//tools/format", ) gazelle( @@ -32,23 +31,6 @@ alias( actual = "@io_bazel_rules_go//go", ) -write_source_files( - name = "bootstrap", - diff_test = False, - executable = True, - files = { - "bin/ocitool-darwin-amd64": "//go/cmd/ocitool", - "bin/ocitool-darwin-arm64": "//go/cmd/ocitool", - "bin/ocitool-linux-amd64": "//go/cmd/ocitool", - "bin/ocitool-linux-arm64": "//go/cmd/ocitool", - }, -) - -alias( - name = "format", - actual = "//tools/format", -) - exports_files( ["WORKSPACE"], visibility = ["//visibility:public"], diff --git a/MODULE.bazel b/MODULE.bazel index aaab418..97196af 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -29,7 +29,6 @@ use_repo( go_deps, "com_github_blakesmith_ar", "com_github_containerd_containerd", - "com_github_containerd_log", "com_github_docker_docker_credential_helpers", "com_github_mitchellh_go_homedir", "com_github_opencontainers_go_digest", @@ -38,7 +37,6 @@ use_repo( "com_github_stretchr_testify", "com_github_urfave_cli_v2", "land_oras_oras_go", - "org_golang_x_sync", ) go_deps.module_override( patch_strip = 1, @@ -54,14 +52,10 @@ oci_pull( name = "ubuntu_noble", # "noble" tag as of 2024-12-30 digest = "sha256:80dd3c3b9c6cecb9f1667e9290b3bc61b78c2678c02cbdae5f0fea92cc6734ab", - registry = "mirror.gcr.io", + registry = "docker.io", repository = "library/ubuntu", ) -register_toolchains( - "@com_github_datadog_rules_oci//:oci_local_toolchain", -) - node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node", dev_dependency = True) node.toolchain(node_version = "16.14.2") diff --git a/README.md b/README.md index 78c2d24..76ae584 100644 --- a/README.md +++ b/README.md @@ -94,20 +94,14 @@ base images. ### Developing -#### Updating dependencies +#### Run the tests -Run `bzl run //:go -- get DEPENDENCY` +Run `bazel test //...` -#### Tests +#### Update the docs -Run the tests using +Run `bzl run //docs:update` -``` -bazel run //:bootstrap -bazel test //... -``` +#### Update go dependencies -You will also need to make it possible for docker to access `ghcr.io` (see the code in -[.github/workflows/main.yaml](.github/workflows/main.yaml) for what we do in CI; an equivalent -method for local build using the [gh CLI](https://github.com/cli/cli) can be found -[here](https://gist.github.com/mislav/e154d707db230dc882d7194ec85d79f6)). +Run `bzl run //:go -- get DEPENDENCY` diff --git a/bin/BUILD.bazel b/bin/BUILD.bazel deleted file mode 100755 index 3e3abb4..0000000 --- a/bin/BUILD.bazel +++ /dev/null @@ -1,16 +0,0 @@ -load("@rules_pkg//pkg:mappings.bzl", "pkg_files") -load("//oci:toolchain.bzl", "create_compiled_oci_toolchains") - -exports_files(glob(["*"])) - -create_compiled_oci_toolchains(name = "oci_toolchain") - -pkg_files( - name = "files", - srcs = glob([ - "*.bzl", - "*.bazel", - ]), - prefix = "bin", - visibility = ["//release:__subpackages__"], -) diff --git a/docs/defs.md b/docs/defs.md index 899e83c..93076f9 100644 --- a/docs/defs.md +++ b/docs/defs.md @@ -50,31 +50,6 @@ oci_image_index(name, manifests | - | List of labels | optional | `[]` | - - -## oci_image_layout - -
-oci_image_layout(name, manifest)
-
- -Writes an OCI Image Index and related blobs to an OCI Image Format -directory. See https://github.com/opencontainers/image-spec/blob/main/image-layout.md -for the specification of the OCI Image Format directory. - -All blobs must be provided in the manifest's OCILayout provider, in the -files attribute. If blobs are missing, creation of the OCI Image Layout -will fail. - -**ATTRIBUTES** - - -| Name | Description | Type | Mandatory | Default | -| :------------- | :------------- | :------------- | :------------- | :------------- | -| name | A unique name for this target. | Name | required | | -| manifest | An OCILayout index to be written to the OCI Image Format directory. | Label | optional | `None` | - - ## oci_push @@ -123,3 +98,60 @@ oci_image_layer | kwargs | Additional arguments to pass to the rule, e.g. `tags` or `visibility` | none | + + +## oci_image_layout + +
+oci_image_layout(name, image_index, gzip, kwargs)
+
+ +Creates targets for an OCI Image Layout directory and a tar file + +See https://github.com/opencontainers/image-spec/blob/main/image-layout.md +for the specification of the OCI Image Format directory. + + +**PARAMETERS** + + +| Name | Description | Default Value | +| :------------- | :------------- | :------------- | +| name | A unique name for the rule | none | +| image_index | An oci_image_index label | none | +| gzip | If true, creates a tar.gz file. If false, creates a tar file | `True` | +| kwargs | Additional arguments to pass to the underlying rules, e.g. tags or visibility | none | + + + + +## oci_pull + +
+oci_pull(name, debug, digest, registry, repo_mapping, repository, scheme, shallow)
+
+ +**ATTRIBUTES** + + +| Name | Description | Type | Mandatory | Default | +| :------------- | :------------- | :------------- | :------------- | :------------- | +| name | A unique name for this repository. | Name | required | | +| debug | Deprecated. Does nothing | Boolean | optional | `False` | +| digest | The digest or tag of the manifest file | String | required | | +| registry | Remote registry host to pull from, e.g. `gcr.io` or `index.docker.io` | String | required | | +| repo_mapping | In `WORKSPACE` context only: a dictionary from local repository name to global repository name. This allows controls over workspace dependency resolution for dependencies of this repository.

For example, an entry `"@foo": "@bar"` declares that, for any time this repository depends on `@foo` (such as a dependency on `@foo//some:target`, it should actually resolve that dependency within globally-declared `@bar` (`@bar//some:target`).

This attribute is _not_ supported in `MODULE.bazel` context (when invoking a repository rule inside a module extension's implementation function). | Dictionary: String -> String | optional | | +| repository | Image path beneath the registry, e.g. `distroless/static` | String | required | | +| scheme | scheme portion of the URL for fetching from the registry | String | optional | `"https"` | +| shallow | Deprecated. Does nothing | Boolean | optional | `False` | + +**ENVIRONMENT VARIABLES** + +This repository rule depends on the following environment variables: +* `DOCKER_CONFIG` +* `REGISTRY_AUTH_FILE` +* `XDG_RUNTIME_DIR` +* `HOME` +* `OCI_ENABLE_OAUTH2_SUPPORT` + + diff --git a/docs/providers.md b/docs/providers.md index 5ac632e..336c93c 100644 --- a/docs/providers.md +++ b/docs/providers.md @@ -26,45 +26,6 @@ OCIDescriptor(file, annotations | String map of aribtrary metadata | - - -## OCIImageIndexManifest - -
-OCIImageIndexManifest(manifests, annotations)
-
- - - -**FIELDS** - - -| Name | Description | -| :------------- | :------------- | -| manifests | List of descriptors | -| annotations | String map of arbitrary metadata | - - - - -## OCIImageManifest - -
-OCIImageManifest(config, layers, annotations)
-
- - - -**FIELDS** - - -| Name | Description | -| :------------- | :------------- | -| config | Descriptor that points to a configuration object | -| layers | List of descriptors | -| annotations | String map of arbitrary metadata | - - ## OCILayout @@ -106,21 +67,3 @@ Refers to any artifact represented by an OCI-like reference URI | digest | a file containing the digest of the artifact | - - -## OCISDK - -
-OCISDK(ocitool)
-
- -The OCI SDK - -**FIELDS** - - -| Name | Description | -| :------------- | :------------- | -| ocitool | - | - - diff --git a/docs/repositories.md b/docs/repositories.md index 92a0463..b155750 100644 --- a/docs/repositories.md +++ b/docs/repositories.md @@ -7,7 +7,7 @@ public repository rules ## oci_pull
-oci_pull(name, debug, digest, registry, repo_mapping, repository, shallow)
+oci_pull(name, debug, digest, registry, repo_mapping, repository, scheme, shallow)
 
**ATTRIBUTES** @@ -16,16 +16,21 @@ oci_pull(name, debug, | Name | Description | Type | Mandatory | Default | | :------------- | :------------- | :------------- | :------------- | :------------- | | name | A unique name for this repository. | Name | required | | -| debug | Enable ocitool debug output | Boolean | optional | `False` | -| digest | - | String | required | | -| registry | - | String | required | | +| debug | Deprecated. Does nothing | Boolean | optional | `False` | +| digest | The digest or tag of the manifest file | String | required | | +| registry | Remote registry host to pull from, e.g. `gcr.io` or `index.docker.io` | String | required | | | repo_mapping | In `WORKSPACE` context only: a dictionary from local repository name to global repository name. This allows controls over workspace dependency resolution for dependencies of this repository.

For example, an entry `"@foo": "@bar"` declares that, for any time this repository depends on `@foo` (such as a dependency on `@foo//some:target`, it should actually resolve that dependency within globally-declared `@bar` (`@bar//some:target`).

This attribute is _not_ supported in `MODULE.bazel` context (when invoking a repository rule inside a module extension's implementation function). | Dictionary: String -> String | optional | | -| repository | - | String | required | | -| shallow | - | Boolean | optional | `True` | +| repository | Image path beneath the registry, e.g. `distroless/static` | String | required | | +| scheme | scheme portion of the URL for fetching from the registry | String | optional | `"https"` | +| shallow | Deprecated. Does nothing | Boolean | optional | `False` | **ENVIRONMENT VARIABLES** This repository rule depends on the following environment variables: -* `OCI_CACHE_DIR` +* `DOCKER_CONFIG` +* `REGISTRY_AUTH_FILE` +* `XDG_RUNTIME_DIR` +* `HOME` +* `OCI_ENABLE_OAUTH2_SUPPORT` diff --git a/examples/go-multiarch-image/BUILD.bazel b/examples/go-multiarch-image/BUILD.bazel index c135558..1c69914 100644 --- a/examples/go-multiarch-image/BUILD.bazel +++ b/examples/go-multiarch-image/BUILD.bazel @@ -1,20 +1,22 @@ -load("@com_github_datadog_rules_oci//oci:defs.bzl", "oci_push") +load("@bazel_skylib//rules:write_file.bzl", "write_file") +load( + "@com_github_datadog_rules_oci//oci:defs.bzl", + "oci_image", + "oci_image_index", + "oci_image_layer", + "oci_image_layout", + "oci_push", +) load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") load(":go.bzl", "go_multiarch_image") go_library( name = "go_default_library", srcs = ["main.go"], - importpath = "github.com/DataDog/rules_oci/tests/go-multiarch-image", + importpath = "github.com/DataDog/rules_oci/examples/go-multiarch-image", visibility = ["//visibility:private"], ) -go_binary( - name = "multiarch", - embed = [":go_default_library"], - visibility = ["//visibility:public"], -) - go_multiarch_image( name = "image", archs = [ @@ -32,3 +34,60 @@ oci_push( registry = "ghcr.io", repository = "datadog/rules_oci/hello-world", ) + +# Add a layer + +write_file( + name = "hello.txt.write_file", + out = "hello.txt", + content = ["Hello, World!"], +) + +oci_image_layer( + name = "layer-hello.txt", + file_map = { + ":hello.txt": "/hello.txt", + }, +) + +_ARCHS = [ + "amd64", + "arm64", +] + +[ + oci_image( + name = "image2.{}".format(arch), + arch = arch, + base = ":image", + layers = [":layer-hello.txt"], + os = "linux", + ) + for arch in _ARCHS +] + +oci_image_index( + name = "image2", + manifests = [ + ":image2.{}".format(arch) + for arch in _ARCHS + ], +) + +oci_image_layout( + name = "image2.dir", + image_index = ":image2", +) + +oci_push( + name = "push2", + manifest = ":image2", + registry = "ghcr.io", + repository = "datadog/rules_oci/hello-world2", +) + +go_binary( + name = "go-multiarch-image", + embed = [":go_default_library"], + visibility = ["//visibility:public"], +) diff --git a/go.mod b/go.mod index 05cf73e..b746dab 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,8 @@ module github.com/DataDog/rules_oci go 1.22.5 require ( - github.com/bazelbuild/bazel-gazelle v0.38.0 github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb github.com/containerd/containerd v1.7.20 - github.com/containerd/log v0.1.0 github.com/docker/docker-credential-helpers v0.8.1 github.com/mitchellh/go-homedir v1.1.0 github.com/opencontainers/go-digest v1.0.0 @@ -14,17 +12,16 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.9.0 github.com/urfave/cli/v2 v2.27.2 - golang.org/x/sync v0.7.0 oras.land/oras-go v1.2.6 ) require ( github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/Microsoft/hcsshim v0.12.3 // indirect - github.com/bazelbuild/buildtools v0.0.0-20240422193413-1429e15ae755 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/containerd/errdefs v0.1.0 // indirect + github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -53,6 +50,7 @@ require ( go.opentelemetry.io/otel v1.26.0 // indirect go.opentelemetry.io/otel/metric v1.26.0 // indirect go.opentelemetry.io/otel/trace v1.26.0 // indirect + golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.22.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434 // indirect google.golang.org/grpc v1.63.2 // indirect diff --git a/go.sum b/go.sum index 8786bba..a040d29 100644 --- a/go.sum +++ b/go.sum @@ -1,17 +1,11 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Microsoft/hcsshim v0.12.3 h1:LS9NXqXhMoqNCplK1ApmVSfB4UnVLRDWRapB6EIlxE0= github.com/Microsoft/hcsshim v0.12.3/go.mod h1:Iyl1WVpZzr+UkzjekHZbV8o5Z9ZkxNGx6CtY2Qg/JVQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/bazelbuild/bazel-gazelle v0.38.0 h1:7SABASdzy94tbvklgX8ThG+1y7ZNl2eFYRVevjOXpgw= -github.com/bazelbuild/bazel-gazelle v0.38.0/go.mod h1:hspieDFb3FIjjhQV/dZjqq8qiVxsQ4rtRiWNZMihEXg= -github.com/bazelbuild/buildtools v0.0.0-20240422193413-1429e15ae755 h1:hqhMmuZiSNwCWVHqnpr4DZfIeZ2/aJF7fs207eg7HZo= -github.com/bazelbuild/buildtools v0.0.0-20240422193413-1429e15ae755/go.mod h1:689QdV3hBP7Vo9dJMmzhoYIyo/9iMhEmHkJcnaPRCbo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -22,13 +16,8 @@ github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuP github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups/v3 v3.0.2 h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKkymb9L0= github.com/containerd/cgroups/v3 v3.0.2/go.mod h1:JUgITrzdFqp42uI2ryGA+ge0ap/nxzYgkGmIcetmErE= @@ -72,8 +61,6 @@ github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQ github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -86,26 +73,12 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -164,7 +137,6 @@ github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQ github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= @@ -238,72 +210,35 @@ go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2L go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= -go.starlark.net v0.0.0-20210223155950-e043a3d3c984/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434 h1:umK/Ey0QEzurTNlsV3R+MfxHAb78HCEX/IkuR+zH4WQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20240509183442-62759503f434/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= @@ -318,7 +253,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= oras.land/oras-go v1.2.6 h1:z8cmxQXBU8yZ4mkytWqXfo6tZcamPwjsuxYU81xJ8Lk= oras.land/oras-go v1.2.6/go.mod h1:OVPc1PegSEe/K8YiLfosrlqlqTN9PUyFvOw5Y9gwrT8= diff --git a/go/cmd/ocitool/BUILD.bazel b/go/cmd/ocitool/BUILD.bazel index 85fe824..cb8dce9 100644 --- a/go/cmd/ocitool/BUILD.bazel +++ b/go/cmd/ocitool/BUILD.bazel @@ -7,13 +7,11 @@ go_library( "createlayer_cmd.go", "desc_helpers.go", "digest_cmd.go", - "gen_cmd.go", "imagelayout_cmd.go", "index_cmd.go", "main.go", "manifest_cmd.go", "publishrules_cmd.go", - "pull_cmd.go", "push_cmd.go", "pushblob_cmd.go", ], @@ -29,15 +27,11 @@ go_library( "@com_github_containerd_containerd//content:go_default_library", "@com_github_containerd_containerd//images:go_default_library", "@com_github_containerd_containerd//platforms:go_default_library", - "@com_github_containerd_log//:go_default_library", "@com_github_opencontainers_go_digest//:go_default_library", "@com_github_opencontainers_image_spec//specs-go:go_default_library", "@com_github_opencontainers_image_spec//specs-go/v1:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", "@com_github_urfave_cli_v2//:go_default_library", - "@gazelle//rule:go_default_library", - "@land_oras_oras_go//pkg/content:go_default_library", - "@org_golang_x_sync//semaphore:go_default_library", ], ) diff --git a/go/cmd/ocitool/gen_cmd.go b/go/cmd/ocitool/gen_cmd.go deleted file mode 100644 index dc7a7df..0000000 --- a/go/cmd/ocitool/gen_cmd.go +++ /dev/null @@ -1,88 +0,0 @@ -package main - -import ( - "context" - "fmt" - "os" - "path/filepath" - - "github.com/DataDog/rules_oci/go/pkg/ociutil" - - "github.com/bazelbuild/bazel-gazelle/rule" - "github.com/containerd/containerd/images" - "github.com/opencontainers/go-digest" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" - log "github.com/sirupsen/logrus" - "github.com/urfave/cli/v2" - orascontent "oras.land/oras-go/pkg/content" -) - -func GenerateBuildFilesCmd(c *cli.Context) error { - allLocalLayoutsPaths := c.StringSlice("layout") - if len(allLocalLayoutsPaths) > 1 { - return fmt.Errorf("too many layouts") - } else if len(allLocalLayoutsPaths) <= 0 { - return fmt.Errorf("need at least one layout") - } - - layoutRootPath := allLocalLayoutsPaths[0] - - layout, err := orascontent.NewOCI(layoutRootPath) - if err != nil { - return err - } - - refs := layout.ListReferences() - refDescs := make([]ocispec.Descriptor, 0, len(refs)) - - for _, r := range refs { - refDescs = append(refDescs, r) - } - - log.Debugf("layout root: %#v", refs) - - err = images.Walk( - context.Background(), - ociutil.GenerateBuildFilesHandler(images.ChildrenHandler(layout), layoutRootPath, layout), - refDescs..., - ) - if err != nil { - return err - } - - imageTargetDigest := c.String("image-digest") - if imageTargetDigest != "" { - err = os.MkdirAll(filepath.Join(layoutRootPath, "image"), 0700) - if err != nil { - return err - } - - imageTargetBuildFilePath := filepath.Join(layoutRootPath, "image", "BUILD.bazel") - imageTargetBuild := rule.EmptyFile(imageTargetBuildFilePath, "") - - aliasRule := rule.NewRule("alias", "image") - aliasRule.SetAttr("actual", dgstToManifestLabel(digest.Digest(imageTargetDigest))) - aliasRule.SetAttr("visibility", ociutil.PublicVisibility) - aliasRule.Insert(imageTargetBuild) - - err = imageTargetBuild.Save(imageTargetBuildFilePath) - if err != nil { - return err - } - - log.Debugf("Created BUILD file in image package") - } - - log.Debugf("Done generating build files") - - return nil -} - -// TODO redeclared a couple other places -func dgstToManifestLabel(dgst digest.Digest) string { - return fmt.Sprintf("//blobs/%s:%s", dgst.Algorithm().String(), dgstToManifestLabelName(dgst)) -} - -func dgstToManifestLabelName(dgst digest.Digest) string { - return fmt.Sprintf("%v-%v-%v", "manifest", dgst.Algorithm().String(), dgst.Encoded()) -} diff --git a/go/cmd/ocitool/main.go b/go/cmd/ocitool/main.go index 21dd325..173104b 100644 --- a/go/cmd/ocitool/main.go +++ b/go/cmd/ocitool/main.go @@ -18,27 +18,6 @@ var app = &cli.App{ return nil }, Commands: []*cli.Command{ - { - Name: "pull", - Usage: "Pull an OCI artifact", - Action: PullCmd, - Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "shallow", - Usage: "Pull only the top level manifests.", - Value: false, - }, - }, - }, - { - Name: "generate-build-files", - Action: GenerateBuildFilesCmd, - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "image-digest", - }, - }, - }, { Name: "create-layer", Action: CreateLayerCmd, diff --git a/go/cmd/ocitool/pull_cmd.go b/go/cmd/ocitool/pull_cmd.go deleted file mode 100644 index 67f97a8..0000000 --- a/go/cmd/ocitool/pull_cmd.go +++ /dev/null @@ -1,78 +0,0 @@ -package main - -import ( - "golang.org/x/sync/semaphore" - - "github.com/DataDog/rules_oci/go/pkg/ociutil" - - "github.com/containerd/containerd/images" - "github.com/containerd/log" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/urfave/cli/v2" - orascontent "oras.land/oras-go/pkg/content" -) - -func PullCmd(c *cli.Context) error { - ref := c.Args().First() - - ctx := log.WithLogger(c.Context, log.G(c.Context).WithField("pull-ref", ref)) - - resolver := ociutil.DefaultResolver() - - name, desc, err := resolver.Resolve(ctx, ref) - if err != nil { - return err - } - - if desc.Annotations == nil { - desc.Annotations = make(map[string]string) - } - - desc.Annotations[ocispec.AnnotationRefName] = name - - log.G(ctx). - WithField("name", name). - WithField("desc", desc). - Debug("resolved") - - layoutPath := c.StringSlice("layout")[0] - - layout, err := orascontent.NewOCI(layoutPath) - if err != nil { - return err - } - - remoteFetcher, err := resolver.Fetcher(ctx, name) - if err != nil { - return err - } - - provider := ociutil.FetchertoProvider(remoteFetcher) - - sem := semaphore.NewWeighted(int64(c.Uint("parallel"))) - - imagesHandler := images.ChildrenHandler(provider) - if c.Bool("shallow") { - imagesHandler = ociutil.ContentTypesFilterHandler(imagesHandler, - ocispec.MediaTypeImageManifest, - ocispec.MediaTypeImageIndex, - ocispec.MediaTypeImageConfig, - images.MediaTypeDockerSchema2Manifest, - images.MediaTypeDockerSchema2ManifestList, - images.MediaTypeDockerSchema2Config, - ) - } - - err = images.Dispatch(ctx, ociutil.CopyContentHandler(imagesHandler, provider, layout), sem, desc) - if err != nil { - return err - } - - layout.AddReference(name, desc) - err = layout.SaveIndex() - if err != nil { - return err - } - - return nil -} diff --git a/go/pkg/ociutil/BUILD.bazel b/go/pkg/ociutil/BUILD.bazel index 933946a..1876dc7 100644 --- a/go/pkg/ociutil/BUILD.bazel +++ b/go/pkg/ociutil/BUILD.bazel @@ -3,7 +3,6 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ - "bazel.go", "desc.go", "diff.go", "fetch.go", @@ -37,7 +36,6 @@ go_library( "@com_github_opencontainers_go_digest//:go_default_library", "@com_github_opencontainers_image_spec//specs-go/v1:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", - "@gazelle//rule:go_default_library", "@land_oras_oras_go//pkg/oras:go_default_library", ], ) diff --git a/go/pkg/ociutil/bazel.go b/go/pkg/ociutil/bazel.go deleted file mode 100644 index c97543d..0000000 --- a/go/pkg/ociutil/bazel.go +++ /dev/null @@ -1,199 +0,0 @@ -package ociutil - -import ( - "context" - "fmt" - "os" - "path/filepath" - "slices" - "sync" - - "github.com/bazelbuild/bazel-gazelle/rule" - "github.com/containerd/containerd/content" - "github.com/containerd/containerd/images" - "github.com/opencontainers/go-digest" - ocispec "github.com/opencontainers/image-spec/specs-go/v1" -) - -// GenerateBuildFilesHandler generates build files while walking a tree. -// TODO Ideally, this should actually be a content.WalkFunc, but ocilayout doesn't -// implement this interface yet -func GenerateBuildFilesHandler(handler images.HandlerFunc, layoutRoot string, provider content.Provider) images.HandlerFunc { - blobBuildFiles := make(map[digest.Algorithm]*rule.File) - var writemx sync.Mutex - - // TODO Currently only supporting SHA256 - blobBuildFiles[digest.SHA256] = rule.EmptyFile(algoBUILDPath(layoutRoot, digest.SHA256), "") - - // Add load statements for all of the oci_* rules - ldBlob := rule.NewLoad("@com_github_datadog_rules_oci//oci/private/repositories:oci_blob.bzl") - ldBlob.Add("oci_blob") - - ldImageManifest := rule.NewLoad("@com_github_datadog_rules_oci//oci/private/repositories:oci_image_manifest.bzl") - ldImageManifest.Add("oci_image_manifest") - - ldImageIndexManifest := rule.NewLoad("@com_github_datadog_rules_oci//oci/private/repositories:oci_image_index_manifest.bzl") - ldImageIndexManifest.Add("oci_image_index_manifest") - - for algo, f := range blobBuildFiles { - ldBlob.Insert(f, 0) - ldImageManifest.Insert(f, 0) - ldImageIndexManifest.Insert(f, 0) - f.Save(algoBUILDPath(layoutRoot, algo)) - } - - // Top level build file for used as an index of the entire layout - layoutBuild := rule.EmptyFile(filepath.Join(layoutRoot, "BUILD.bazel"), "") - - ldLayout := rule.NewLoad("@com_github_datadog_rules_oci//oci/private/repositories:oci_layout_index.bzl") - ldLayout.Add("oci_layout_index") - ldLayout.Insert(layoutBuild, 0) - - indexRule := rule.NewRule("oci_layout_index", "layout") - indexRule.SetAttr("visibility", PublicVisibility) - indexRule.Insert(layoutBuild) - - layoutBuild.Save(filepath.Join(layoutRoot, "BUILD.bazel")) - - // It's possible to encounter the same blob multiple times. We record the - // ones we've already encountered so we don't process them twice. - // Duplicate rules make bazel sad. - handledBlobs := make([]string, 0) - - return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { - writemx.Lock() - defer writemx.Unlock() - - if slices.Contains(handledBlobs, desc.Digest.String()) { - // We've already seen this blob; do nothing. - return handler(ctx, desc) - } - - if !blobExists(layoutRoot, desc.Digest) { - return nil, images.ErrSkipDesc - } - - algo := desc.Digest.Algorithm() - f, ok := blobBuildFiles[algo] - if !ok { - return nil, fmt.Errorf("no build file for algo '%v'", algo) - } - - // Insert a rule for each blob - blobRuleFromDescriptor(desc).Insert(f) - - // if the manifest is an manifest or index, add an additional rule to - // complete graph - switch desc.MediaType { - case ocispec.MediaTypeImageManifest, images.MediaTypeDockerSchema2Manifest: - manifest, err := ImageManifestFromProvider(ctx, provider, desc) - if err != nil { - return nil, err - } - - imageManifestRule(desc, manifest).Insert(f) - case ocispec.MediaTypeImageIndex, images.MediaTypeDockerSchema2ManifestList: - index, err := ImageIndexFromProvider(ctx, provider, desc) - if err != nil { - return nil, err - } - - imageIndexManifestRule(desc, index).Insert(f) - } - - // Save all BUILD files - for algo, bf := range blobBuildFiles { - err := bf.Save(algoBUILDPath(layoutRoot, algo)) - if err != nil { - return nil, err - } - } - - ldLayout.Insert(layoutBuild, 0) - indexRule.SetAttr("blobs", append(indexRule.AttrStrings("blobs"), dgstToLabel(desc.Digest))) - err := layoutBuild.Save(filepath.Join(layoutRoot, "BUILD.bazel")) - if err != nil { - return nil, err - } - - handledBlobs = append(handledBlobs, desc.Digest.String()) - return handler(ctx, desc) - } -} - -func blobExists(layoutRoot string, dgst digest.Digest) bool { - _, err := os.Stat(descToFilePath(layoutRoot, dgst)) - return !os.IsNotExist(err) -} - -func descToFilePath(root string, dgst digest.Digest) string { - return filepath.Join(root, "blobs", dgst.Algorithm().String(), dgst.Encoded()) -} - -func algoBUILDPath(root string, algo digest.Algorithm) string { - return filepath.Join(root, "blobs", algo.String(), "BUILD.bazel") -} - -func dgstToManifestLabelName(dgst digest.Digest) string { - return fmt.Sprintf("manifest-%v-%v", dgst.Algorithm().String(), dgst.Encoded()) -} - -func dgstToLabelName(dgst digest.Digest) string { - return fmt.Sprintf("%v-%v", dgst.Algorithm().String(), dgst.Encoded()) -} - -func dgstToLabel(dgst digest.Digest) string { - return fmt.Sprintf("//blobs/%s:%s", dgst.Algorithm().String(), dgstToLabelName(dgst)) -} - -func descriptorListToLabels(desc []ocispec.Descriptor) []string { - layerTargets := make([]string, 0, len(desc)) - for _, desc := range desc { - layerTargets = append(layerTargets, dgstToLabel(desc.Digest)) - } - - return layerTargets -} - -var ( - PublicVisibility = []string{"//visibility:public"} -) - -func blobRuleFromDescriptor(desc ocispec.Descriptor) *rule.Rule { - r := rule.NewRule("oci_blob", dgstToLabelName(desc.Digest)) - r.SetAttr("file", desc.Digest.Encoded()) - r.SetAttr("digest", desc.Digest.String()) - r.SetAttr("media_type", desc.MediaType) - r.SetAttr("size", desc.Size) - r.SetAttr("annotations", desc.Annotations) - r.SetAttr("urls", desc.URLs) - r.SetAttr("visibility", PublicVisibility) - - return r -} - -func imageManifestRule(desc ocispec.Descriptor, manifest ocispec.Manifest) *rule.Rule { - r := rule.NewRule("oci_image_manifest", dgstToManifestLabelName(desc.Digest)) - - r.SetAttr("descriptor", dgstToLabel(desc.Digest)) - r.SetAttr("config", dgstToLabel(manifest.Config.Digest)) - // TODO(griffin) Not handling shallow well - //r.SetAttr("layers", descriptorListToLabels(manifest.Layers)) - r.SetAttr("annotations", manifest.Annotations) - r.SetAttr("visibility", PublicVisibility) - r.SetAttr("layout", "//:layout") - - return r -} - -func imageIndexManifestRule(desc ocispec.Descriptor, manifest ocispec.Index) *rule.Rule { - r := rule.NewRule("oci_image_index_manifest", dgstToManifestLabelName(desc.Digest)) - - r.SetAttr("descriptor", dgstToLabel(desc.Digest)) - r.SetAttr("manifests", descriptorListToLabels(manifest.Manifests)) - r.SetAttr("annotations", manifest.Annotations) - r.SetAttr("visibility", PublicVisibility) - r.SetAttr("layout", "//:layout") - - return r -} diff --git a/go/pkg/ociutil/fs.go b/go/pkg/ociutil/fs.go index 6c9f964..12fc50c 100644 --- a/go/pkg/ociutil/fs.go +++ b/go/pkg/ociutil/fs.go @@ -4,10 +4,12 @@ import ( "context" "io" "io/fs" + "path/filepath" "sync" "github.com/containerd/containerd/content" + "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) @@ -94,3 +96,7 @@ func (f *fsFile) ReadAt(p []byte, off int64) (int, error) { return n, nil } + +func descToFilePath(root string, dgst digest.Digest) string { + return filepath.Join(root, "blobs", dgst.Algorithm().String(), dgst.Encoded()) +} diff --git a/oci/BUILD.bazel b/oci/BUILD.bazel index 48cff8a..697f3fa 100644 --- a/oci/BUILD.bazel +++ b/oci/BUILD.bazel @@ -9,11 +9,6 @@ exports_files( visibility = ["//:__subpackages__"], ) -toolchain_type( - name = "toolchain", - visibility = ["//visibility:public"], -) - debug_flag( name = "debug", build_setting_default = False, @@ -22,9 +17,7 @@ debug_flag( bzl_library( name = "defs", - srcs = [ - ":defs.bzl", - ], + srcs = ["defs.bzl"], visibility = ["//visibility:public"], deps = [ "//oci:providers.bzl", @@ -35,26 +28,29 @@ bzl_library( "//oci/private:oci_image_layer.bzl", "//oci/private:oci_image_layout.bzl", "//oci/private:oci_push.bzl", + "//oci/private/repositories:authn.bzl", + "//oci/private/repositories:download.bzl", + "//oci/private/repositories:oci_pull.bzl", + "@aspect_bazel_lib//lib:base64", + "@aspect_bazel_lib//lib:repo_utils", "@aspect_bazel_lib//lib:stamping", + "@bazel_skylib//lib:paths", + "@bazel_skylib//lib:versions", + "@rules_pkg//pkg:bzl_srcs", ], ) -bzl_library( - name = "providers", - srcs = [ - ":providers.bzl", - ], - visibility = ["//visibility:public"], -) - bzl_library( name = "repositories", - srcs = [ - ":repositories.bzl", - ], + srcs = ["repositories.bzl"], visibility = ["//visibility:public"], deps = [ + "//oci/private/repositories:authn.bzl", + "//oci/private/repositories:download.bzl", "//oci/private/repositories:oci_pull.bzl", + "@aspect_bazel_lib//lib:base64", + "@aspect_bazel_lib//lib:repo_utils", + "@bazel_skylib//lib:versions", ], ) @@ -67,3 +63,15 @@ pkg_files( prefix = "oci", visibility = ["//release:__subpackages__"], ) + +bzl_library( + name = "providers", + srcs = ["providers.bzl"], + visibility = ["//visibility:public"], +) + +bzl_library( + name = "toolchain", + srcs = ["toolchain.bzl"], + visibility = ["//visibility:public"], +) diff --git a/oci/defs.bzl b/oci/defs.bzl index 0af379d..9c48101 100644 --- a/oci/defs.bzl +++ b/oci/defs.bzl @@ -5,9 +5,13 @@ load("//oci/private:oci_image_index.bzl", _oci_image_index = "oci_image_index") load("//oci/private:oci_image_layer.bzl", _oci_image_layer = "oci_image_layer") load("//oci/private:oci_image_layout.bzl", _oci_image_layout = "oci_image_layout") load("//oci/private:oci_push.bzl", _oci_push = "oci_push") +load("//oci/private/repositories:oci_pull.bzl", _oci_pull = "oci_pull") oci_image = _oci_image oci_image_index = _oci_image_index oci_image_layer = _oci_image_layer oci_image_layout = _oci_image_layout oci_push = _oci_push + +# TODO(brian.myers): Remove this (from defs.bzl, not repositories.bzl) once consumers no longer use it +oci_pull = _oci_pull diff --git a/oci/private/BUILD.bazel b/oci/private/BUILD.bazel index bf21dcf..703d0cd 100644 --- a/oci/private/BUILD.bazel +++ b/oci/private/BUILD.bazel @@ -1,5 +1,7 @@ load("@rules_pkg//pkg:mappings.bzl", "pkg_files") +# gazelle:lang go + exports_files( glob(["*.bzl"]), visibility = ["//oci:__subpackages__"], diff --git a/oci/private/oci_image.bzl b/oci/private/oci_image.bzl index 4a51013..848bd50 100644 --- a/oci/private/oci_image.bzl +++ b/oci/private/oci_image.bzl @@ -4,8 +4,6 @@ load("//oci:providers.bzl", "OCIDescriptor", "OCILayout") load(":common.bzl", "get_descriptor_file") def _impl(ctx): - toolchain = ctx.toolchains["//oci:toolchain"] - base_desc = get_descriptor_file(ctx, ctx.attr.base[OCIDescriptor]) base_layout = ctx.attr.base[OCILayout] @@ -37,7 +35,7 @@ def _impl(ctx): ) ctx.actions.run( - executable = toolchain.sdk.ocitool, + executable = ctx.executable._ocitool, arguments = [ "--layout={}".format(base_layout.blob_index.path), "append-layers", @@ -143,6 +141,11 @@ oci_image = rule( will cause that label to be deleted. For backwards compatibility, if this is not set, then the value of annotations will be used instead.""", ), + "_ocitool": attr.label( + allow_single_file = True, + cfg = "exec", + default = "//go/cmd/ocitool", + executable = True, + ), }, - toolchains = ["//oci:toolchain"], ) diff --git a/oci/private/oci_image_index.bzl b/oci/private/oci_image_index.bzl index 1faef60..e6bdd66 100644 --- a/oci/private/oci_image_index.bzl +++ b/oci/private/oci_image_index.bzl @@ -4,8 +4,6 @@ load("//oci:providers.bzl", "OCIDescriptor", "OCILayout") load(":common.bzl", "get_descriptor_file") def _impl(ctx): - toolchain = ctx.toolchains["//oci:toolchain"] - layout_files = depset(None, transitive = [m[OCILayout].files for m in ctx.attr.manifests]) index_desc_file = ctx.actions.declare_file("{}.index.descriptor.json".format(ctx.label.name)) @@ -23,7 +21,7 @@ def _impl(ctx): ] ctx.actions.run( - executable = toolchain.sdk.ocitool, + executable = ctx.executable._ocitool, arguments = ["--layout={}".format(m[OCILayout].blob_index.path) for m in ctx.attr.manifests] + [ "create-index", @@ -63,6 +61,11 @@ oci_image_index = rule( doc = """ """, ), + "_ocitool": attr.label( + allow_single_file = True, + cfg = "exec", + default = "//go/cmd/ocitool", + executable = True, + ), }, - toolchains = ["//oci:toolchain"], ) diff --git a/oci/private/oci_image_layer.bzl b/oci/private/oci_image_layer.bzl index 28cab58..42beaa4 100644 --- a/oci/private/oci_image_layer.bzl +++ b/oci/private/oci_image_layer.bzl @@ -38,12 +38,10 @@ def oci_image_layer( ) def _impl(ctx): - toolchain = ctx.toolchains["//oci:toolchain"] - descriptor_file = ctx.actions.declare_file("{}.descriptor.json".format(ctx.label.name)) ctx.actions.run( - executable = toolchain.sdk.ocitool, + executable = ctx.executable._ocitool, arguments = [ "create-layer", "--out={}".format(ctx.outputs.layer.path), @@ -78,8 +76,13 @@ _oci_image_layer = rule( "directory": attr.string(), "symlinks": attr.string_dict(), "file_map": attr.label_keyed_string_dict(allow_files = True), + "_ocitool": attr.label( + allow_single_file = True, + cfg = "exec", + default = "//go/cmd/ocitool", + executable = True, + ), }, - toolchains = ["//oci:toolchain"], outputs = { "layer": "%{name}-layer.tar.gz", }, diff --git a/oci/private/oci_image_layout.bzl b/oci/private/oci_image_layout.bzl index d8fc0ef..62a9648 100644 --- a/oci/private/oci_image_layout.bzl +++ b/oci/private/oci_image_layout.bzl @@ -1,21 +1,76 @@ """A rule to create a directory in OCI Image Layout format.""" +load("@rules_pkg//pkg:mappings.bzl", "pkg_files") +load("@rules_pkg//pkg:pkg.bzl", "pkg_tar") load("//oci:providers.bzl", "OCIDescriptor", "OCILayout") load(":debug_flag.bzl", "DebugInfo") -def _oci_image_layout_impl(ctx): - toolchain = ctx.toolchains["//oci:toolchain"] +def oci_image_layout( + *, + name, + image_index, + gzip = True, + **kwargs): + """Creates targets for an OCI Image Layout directory and a tar file - layout = ctx.attr.manifest[OCILayout] + See https://github.com/opencontainers/image-spec/blob/main/image-layout.md + for the specification of the OCI Image Format directory. + + Args: + name: A unique name for the rule + image_index: An oci_image_index label + gzip: If true, creates a tar.gz file. If false, creates a tar file + **kwargs: Additional arguments to pass to the underlying rules, e.g. + tags or visibility + """ + _oci_image_layout( + name = name, + image_index = image_index, + **kwargs + ) + + kwargs_copy = dict(kwargs) + kwargs_copy.pop("visibility", None) + pkg_files( + name = "{}.pkg_files".format(name), + srcs = [":{}".format(name)], + strip_prefix = ".", + renames = { + ":{}".format(name): "./", + }, + visibility = ["//visibility:private"], + **kwargs_copy + ) + + if gzip: + pkg_tar( + name = "{}.tar".format(name), + extension = "tar.gz", + srcs = ["{}.pkg_files".format(name)], + package_file_name = "{}.tar.gz".format(name), + strip_prefix = ".", + **kwargs + ) + else: + pkg_tar( + name = "{}.tar".format(name), + srcs = ["{}.pkg_files".format(name)], + package_file_name = "{}.tar".format(name), + strip_prefix = ".", + **kwargs + ) + +def _impl(ctx): + layout = ctx.attr.image_index[OCILayout] # layout_files contains all available blobs for the image. layout_files = ",".join([p.path for p in layout.files.to_list()]) - descriptor = ctx.attr.manifest[OCIDescriptor] + descriptor = ctx.attr.image_index[OCIDescriptor] out_dir = ctx.actions.declare_directory(ctx.label.name) ctx.actions.run( - executable = toolchain.sdk.ocitool, + executable = ctx.executable._ocitool, arguments = [ "--layout={layout}".format(layout = layout.blob_index.path), "--debug={debug}".format(debug = str(ctx.attr._debug[DebugInfo].debug)), @@ -31,44 +86,30 @@ def _oci_image_layout_impl(ctx): "--layout-files={layout_files}".format(layout_files = layout_files), "--out-dir={out_dir}".format(out_dir = out_dir.path), ], - inputs = - depset( - direct = ctx.files.manifest + [layout.blob_index], - transitive = [layout.files], - ), - outputs = [ - out_dir, - ], - use_default_shell_env = True, + inputs = depset( + direct = ctx.files.image_index + [layout.blob_index], + transitive = [layout.files], + ), + outputs = [out_dir], ) - return DefaultInfo(files = depset([out_dir])) - -oci_image_layout = rule( - doc = """ - Writes an OCI Image Index and related blobs to an OCI Image Format - directory. See https://github.com/opencontainers/image-spec/blob/main/image-layout.md - for the specification of the OCI Image Format directory. + return [ + DefaultInfo(files = depset([out_dir])), + ] - All blobs must be provided in the manifest's OCILayout provider, in the - files attribute. If blobs are missing, creation of the OCI Image Layout - will fail. - """, - implementation = _oci_image_layout_impl, +_oci_image_layout = rule( + implementation = _impl, attrs = { - "manifest": attr.label( - doc = """ - An OCILayout index to be written to the OCI Image Format directory. - """, - providers = [OCILayout], - ), + "image_index": attr.label(providers = [OCILayout]), "_debug": attr.label( default = "//oci:debug", providers = [DebugInfo], ), + "_ocitool": attr.label( + allow_single_file = True, + cfg = "exec", + default = "//go/cmd/ocitool", + executable = True, + ), }, - provides = [ - DefaultInfo, - ], - toolchains = ["//oci:toolchain"], ) diff --git a/oci/private/oci_push.bzl b/oci/private/oci_push.bzl index 8940610..c57f1df 100644 --- a/oci/private/oci_push.bzl +++ b/oci/private/oci_push.bzl @@ -5,8 +5,6 @@ load("//oci:providers.bzl", "OCIDescriptor", "OCILayout", "OCIReferenceInfo") load(":debug_flag.bzl", "DebugInfo") def _impl(ctx): - toolchain = ctx.toolchains["//oci:toolchain"] - layout = ctx.attr.manifest[OCILayout] ref = "{registry}/{repository}".format( @@ -65,7 +63,7 @@ done digest_file = ctx.actions.declare_file("{name}.digest".format(name = ctx.label.name)) ctx.actions.run( - executable = toolchain.sdk.ocitool, + executable = ctx.executable._ocitool, arguments = [ "digest", "--desc={desc}".format(desc = ctx.attr.manifest[OCIDescriptor].descriptor_file.path), @@ -104,7 +102,7 @@ done export OCI_REFERENCE={ref}@$(cat {digest}) """.format( root = ctx.bin_dir.path, - tool = toolchain.sdk.ocitool.short_path, + tool = ctx.executable._ocitool.short_path, layout = layout.blob_index.short_path, desc = ctx.attr.manifest[OCIDescriptor].descriptor_file.short_path, ref = ref, @@ -122,7 +120,13 @@ done DefaultInfo( runfiles = ctx.runfiles( files = layout.files.to_list() + - [toolchain.sdk.ocitool, ctx.attr.manifest[OCIDescriptor].descriptor_file, layout.blob_index, digest_file, tag_file], + [ + ctx.executable._ocitool, + ctx.attr.manifest[OCIDescriptor].descriptor_file, + layout.blob_index, + digest_file, + tag_file, + ], ), ), OCIReferenceInfo( @@ -198,9 +202,14 @@ oci_push = rule( default = "//oci:debug", providers = [DebugInfo], ), + "_ocitool": attr.label( + allow_single_file = True, + cfg = "exec", + default = "//go/cmd/ocitool", + executable = True, + ), }, **STAMP_ATTRS), provides = [ OCIReferenceInfo, ], - toolchains = ["//oci:toolchain"], ) diff --git a/oci/private/repositories/authn.bzl b/oci/private/repositories/authn.bzl new file mode 100644 index 0000000..2d4ae35 --- /dev/null +++ b/oci/private/repositories/authn.bzl @@ -0,0 +1,352 @@ +# Copied from https://github.com/bazel-contrib/rules_oci/blob/843eb01b152b884fe731a3fb4431b738ad00ea60/oci/private/authn.bzl +# which is licensed under the Apache License, Version 2.0. +# +# Then slightly modified to fit the needs of this project + +"repository rule that locates the .docker/config.json or containers/auth.json file." + +load("@aspect_bazel_lib//lib:base64.bzl", "base64") +load("@aspect_bazel_lib//lib:repo_utils.bzl", "repo_utils") + +# Unfortunately bazel downloader doesn't let us sniff the WWW-Authenticate header, therefore we need to +# keep a map of known registries that require us to acquire a temporary token for authentication. +_WWW_AUTH = { + "index.docker.io": { + "realm": "auth.docker.io/token", + "scope": "repository:{repository}:pull", + "service": "registry.docker.io", + }, + "public.ecr.aws": { + "realm": "{registry}/token", + "scope": "repository:{repository}:pull", + "service": "{registry}", + }, + "ghcr.io": { + "realm": "{registry}/token", + "scope": "repository:{repository}:pull", + "service": "{registry}/token", + }, + "cgr.dev": { + "realm": "{registry}/token", + "scope": "repository:{repository}:pull", + "service": "{registry}", + }, + ".azurecr.io": { + "realm": "{registry}/oauth2/token", + "scope": "repository:{repository}:pull", + "service": "{registry}", + }, + "registry.gitlab.com": { + "realm": "gitlab.com/jwt/auth", + "scope": "repository:{repository}:pull", + "service": "container_registry", + }, + ".app.snowflake.com": { + "realm": "{registry}/v2/token", + "scope": "repository:{repository}:pull", + "service": "{registry}", + }, + "docker.elastic.co": { + "realm": "docker-auth.elastic.co/auth", + "scope": "repository:{repository}:pull", + "service": "token-service", + }, + "quay.io": { + "realm": "{registry}/v2/auth", + "scope": "repository:{repository}:pull", + "service": "{registry}", + }, + "nvcr.io": { + "realm": "{registry}/proxy_auth", + "scope": "repository:{repository}:pull", + "service": "{registry}", + }, + "registry.ddbuild.io": { + "realm": "registry-auth.ddbuild.io/token", + "scope": "repository:{repository}:pull", + "service": "registry.ddbuild.io", + }, +} + +def _strip_host(url): + # TODO: a principled way of doing this + return url.replace("http://", "").replace("https://", "").replace("/v1/", "") + +# Path of the auth file is determined by the order described here; +# https://github.com/google/go-containerregistry/tree/main/pkg/authn#tldr-for-consumers-of-this-package +def _get_auth_file_path(rctx): + HOME = repo_utils.get_env_var(rctx, "HOME", "ERR_NO_HOME_SET") + + # this is the standard path where registry credentials are stored + # https://docs.docker.com/engine/reference/commandline/cli/#configuration-files + DOCKER_CONFIG = "{}/.docker".format(HOME) + + # set DOCKER_CONFIG to $DOCKER_CONFIG env if present + if "DOCKER_CONFIG" in rctx.os.environ: + DOCKER_CONFIG = rctx.os.environ["DOCKER_CONFIG"] + + config_path = "{}/config.json".format(DOCKER_CONFIG) + + if _file_exists(rctx, config_path): + return config_path + + # https://docs.podman.io/en/latest/markdown/podman-login.1.html#authfile-path + XDG_RUNTIME_DIR = "{}/.config".format(HOME) + + # set XDG_RUNTIME_DIR to $XDG_RUNTIME_DIR env if present + if "XDG_RUNTIME_DIR" in rctx.os.environ: + XDG_RUNTIME_DIR = rctx.os.environ["XDG_RUNTIME_DIR"] + + config_path = "{}/containers/auth.json".format(XDG_RUNTIME_DIR) + + # podman support overriding the standard path for the auth file via this special environment variable. + # https://docs.podman.io/en/latest/markdown/podman-login.1.html#authfile-path + if "REGISTRY_AUTH_FILE" in rctx.os.environ: + config_path = rctx.os.environ["REGISTRY_AUTH_FILE"] + + if _file_exists(rctx, config_path): + return config_path + + return None + +def _fetch_auth_via_creds_helper(rctx, raw_host, helper_name, allow_fail = False): + executable = "{}.sh".format(helper_name) + rctx.file( + executable, + content = """\ +#!/usr/bin/env bash +exec "docker-credential-{}" get <<< "$1" """.format(helper_name), + ) + result = rctx.execute([rctx.path(executable), raw_host]) + rctx.delete(rctx.path(executable)) + if result.return_code: + if not allow_fail: + fail("credential helper failed: \nSTDOUT:\n{}\nSTDERR:\n{}".format(result.stdout, result.stderr)) + else: + return {} + + response = json.decode(result.stdout) + + return { + "type": "basic", + "login": response["Username"], + "password": response["Secret"], + } + +OAUTH_2_SCRIPT_CURL = """\ +url=$1 +service=$2 +scope=$3 +refresh_token=$4 + +response=$(curl --silent --show-error --fail --request POST --data "grant_type=refresh_token&service=$service&scope=$scope&refresh_token=$refresh_token" $url) + +if [ $? -ne 0 ]; then + exit 1 +fi + +echo "$response" +""" + +OAUTH_2_SCRIPT_WGET = """\ +url=$1 +service=$2 +scope=$3 +refresh_token=$4 + +response=$(wget --quiet --output-document=- --post-data "grant_type=refresh_token&service=$service&scope=$scope&refresh_token=$refresh_token" $url) + +if [ $? -ne 0 ]; then + exit 1 +fi + +echo "$response" +""" + +def _oauth2(rctx, realm, scope, service, secret): + if rctx.which("curl"): + executable = "oauth2.sh" + rctx.file(executable, content = OAUTH_2_SCRIPT_CURL) + result = rctx.execute(["bash", rctx.path(executable), realm, service, scope, secret]) + elif rctx.which("wget"): + executable = "oauth2.sh" + rctx.file(executable, content = OAUTH_2_SCRIPT_WGET) + result = rctx.execute(["bash", rctx.path(executable), realm, service, scope, secret]) + else: + fail("oauth2 failed, could not find either of: curl, wget, powershell") + + if result.return_code: + fail("oauth2 failed:\nSTDOUT:\n{}\nSTDERR:\n{}".format(result.stdout, result.stderr)) + return result.stdout + +def _get_auth(rctx, state, registry): + # if we have a cached auth for this registry then just return it. + # this will prevent repetitive calls to external cred helper binaries. + if registry in state["auth"]: + return state["auth"][registry] + + pattern = {} + config = state["config"] + + # first look into per registry credHelpers if it exists + if "credHelpers" in config: + for host_raw in config["credHelpers"]: + host = _strip_host(host_raw) + if host == registry: + helper_val = config["credHelpers"][host_raw] + pattern = _fetch_auth_via_creds_helper(rctx, host_raw, helper_val) + + # if no match for per registry credential helper for the host then look into auths dictionary + if "auths" in config and len(pattern.keys()) == 0: + for host_raw in config["auths"]: + host = _strip_host(host_raw) + if host == registry: + auth_val = config["auths"][host_raw] + + if len(auth_val.keys()) == 0: + # zero keys indicates that credentials are stored in credsStore helper. + pattern = _fetch_auth_via_creds_helper(rctx, host_raw, config["credsStore"]) + + elif "auth" in auth_val: + # base64 encoded plaintext username and password + raw_auth = auth_val["auth"] + login, sep, password = base64.decode(raw_auth).partition(":") + if not sep: + fail("auth string must be in form username:password") + if not password and "identitytoken" in auth_val: + password = auth_val["identitytoken"] + pattern = { + "type": "basic", + "login": login, + "password": password, + } + + elif "username" in auth_val and "password" in auth_val: + # plain text username and password + pattern = { + "type": "basic", + "login": auth_val["username"], + "password": auth_val["password"], + } + + # look for generic credentials-store all lookups for host-specific auth fails + if "credsStore" in config and len(pattern.keys()) == 0: + pattern = _fetch_auth_via_creds_helper(rctx, registry, config["credsStore"], allow_fail = True) + + # cache the result so that we don't do this again unnecessarily. + state["auth"][registry] = pattern + + return pattern + +IDENTITY_TOKEN_WARNING = """\ +OAuth2 support for oci_pull is highly experimental and is not enabled by default. + +We may change or abandon it without a notice. Use it at your own peril! + +To enable this feature, add `common --repo_env=OCI_ENABLE_OAUTH2_SUPPORT=1` to the `.bazelrc` file. +""" + +def _get_token(rctx, state, registry, repository): + pattern = _get_auth(rctx, state, registry) + + for registry_pattern in _WWW_AUTH.keys(): + if (registry == registry_pattern) or registry.endswith(registry_pattern): + www_authenticate = _WWW_AUTH[registry_pattern] + url = "https://{realm}?scope={scope}&service={service}".format( + realm = www_authenticate["realm"].format(registry = registry), + service = www_authenticate["service"].format(registry = registry), + scope = www_authenticate["scope"].format(repository = repository), + ) + + # if a token for this repository and registry is acquired, use that instead. + if url in state["token"]: + return state["token"][url] + + auth = None + if pattern.get("login", None) == "": + if not rctx.os.environ.get("OCI_ENABLE_OAUTH2_SUPPORT"): + fail(IDENTITY_TOKEN_WARNING) + + response = _oauth2( + rctx = rctx, + realm = "https://" + www_authenticate["realm"].format(registry = registry), + scope = www_authenticate["scope"].format(repository = repository), + service = www_authenticate["service"].format(registry = registry), + secret = pattern["password"], + ) + + rctx.file( + "www-authenticate.json", + content = response, + executable = False, + ) + else: + rctx.download( + url = [url], + output = "www-authenticate.json", + # optionally, sending the credentials to authenticate using the credentials. + # this is for fetching from private repositories that require WWW-Authenticate + auth = {url: pattern}, + ) + + auth_raw = rctx.read("www-authenticate.json") + auth = json.decode(auth_raw) + + token = "" + if "token" in auth: + token = auth["token"] + if "access_token" in auth: + token = auth["access_token"] + if token == "": + fail("could not find token in neither field 'token' nor 'access_token' in the response from the registry") + pattern = { + "type": "pattern", + "pattern": "Bearer ", + "password": token, + } + + # put the token into cache so that we don't do the token exchange again. + state["token"][url] = pattern + return pattern + +NO_CONFIG_FOUND_ERROR = """\ +Could not find the `$HOME/.docker/config.json` and `$XDG_RUNTIME_DIR/containers/auth.json` file + +Running one of `podman login`, `docker login`, `crane login` may help. +""" + +def _explain(state): + if not state["config"]: + return NO_CONFIG_FOUND_ERROR + return None + +def _new_auth(rctx, config_path = None): + if not config_path: + config_path = _get_auth_file_path(rctx) + config = {} + if config_path: + config = json.decode(rctx.read(config_path)) + state = { + "config": config, + "auth": {}, + "token": {}, + } + return struct( + get_token = lambda reg, repo: _get_token(rctx, state, reg, repo), + explain = lambda: _explain(state), + ) + +authn = struct( + new = _new_auth, + ENVIRON = [ + "DOCKER_CONFIG", + "REGISTRY_AUTH_FILE", + "XDG_RUNTIME_DIR", + "HOME", + "OCI_ENABLE_OAUTH2_SUPPORT", + ], +) + +def _file_exists(rctx, path): + result = rctx.execute(["stat", path]) + return result.return_code == 0 diff --git a/oci/private/repositories/download.bzl b/oci/private/repositories/download.bzl new file mode 100644 index 0000000..24bbdc8 --- /dev/null +++ b/oci/private/repositories/download.bzl @@ -0,0 +1,146 @@ +""" download utilities """ + +load("@bazel_skylib//lib:versions.bzl", "versions") + +MEDIA_TYPE_DOCKER_INDEX = "application/vnd.docker.distribution.manifest.list.v2+json" +MEDIA_TYPE_DOCKER_MANIFEST = "application/vnd.docker.distribution.manifest.v2+json" +MEDIA_TYPE_OCI_INDEX = "application/vnd.oci.image.index.v1+json" +MEDIA_TYPE_OCI_MANIFEST = "application/vnd.oci.image.manifest.v1+json" + +_RESOURCE_BLOB = "blobs" +_RESOURCE_INDEX_OR_MANIFEST = "manifests" + +def download_blob( + rctx, + *, + authn, # authn object + digest, # str + non_blocking = None): # list[PendingDownload] | None + # -> struct(path: str, sha256: str) | None + """Download a blob by digest and write it to the blobs/sha256 directory + + Args: + rctx: The repository context + authn: An authn object + digest: The digest of the blob to download + non_blocking: + - If you want the download to block, set this to None (the default) + - If you want the download to be non-blocking, pass a list here. + This is an outparam. The function will append a PendingDownload + object to this list. Later, you can call .wait() on that object + to block until the download is complete + """ + sha256 = digest[len("sha256:"):] + return _download( + rctx, + authn = authn, + digest = digest, + outpath = rctx.path("blobs/sha256/{}".format(sha256)), + resource = _RESOURCE_BLOB, + non_blocking = non_blocking, + ) + +def download_index_or_manifest( + rctx, + *, + authn, + digest, # str + outpath, # str + non_blocking = None): # list[PendingDownload] | None + # -> struct(path: str, sha256: str) | None + """Download an index or manifest by digest + + Args: + rctx: The repository context + authn: An authn object + digest: The digest of the blob to download + outpath: The path to write the downloaded index or manifest to + non_blocking: + - If you want the download to block, set this to None (the default) + - If you want the download to be non-blocking, pass a list here. + This is an outparam. The function will append a PendingDownload + object to this list. Later, you can call .wait() on that object + to block until the download is complete + """ + return _download( + rctx, + authn = authn, + digest = digest, + outpath = rctx.path(outpath), + resource = _RESOURCE_INDEX_OR_MANIFEST, + non_blocking = non_blocking, + ) + +def _download( + rctx, + *, + authn, + digest, # str + outpath, # str + resource, # str + non_blocking = None): # list[result] | None + # -> struct(path: str, sha256: str) | None + + non_blocking_type = type(non_blocking) + if non_blocking_type == type([]): + block = False + elif non_blocking_type == type(None): + block = True + else: + fail("Wrong type for non_blocking parameter. Got {non_blocking_type}. Expected a list or None") + if not digest.startswith("sha256:"): + fail("Invalid value for digest parameter. Must start with 'sha256:'") + + auth = authn.get_token(rctx.attr.registry, rctx.attr.repository) + + sha256 = digest[len("sha256:"):] + + url = "{scheme}://{registry}/v2/{repository}/{resource}/{digest}".format( + scheme = rctx.attr.scheme, + registry = rctx.attr.registry, + repository = rctx.attr.repository, + resource = resource, + digest = digest, + ) + + is_gt_bazel_7_1 = versions.is_at_least("7.1.0", versions.get()) + + # On Bazel 7.1.0 and later, use non-blocking download (if requested) and forward headers + extra_args = {} + if is_gt_bazel_7_1: + extra_args["block"] = block + extra_args["headers"] = { + "Accept": ",".join([ + MEDIA_TYPE_DOCKER_INDEX, + MEDIA_TYPE_DOCKER_MANIFEST, + MEDIA_TYPE_OCI_INDEX, + MEDIA_TYPE_OCI_MANIFEST, + ]), + "Docker-Distribution-API-Version": "registry/2.0", + } + + res = rctx.download( + auth = {url: auth}, + output = outpath, + sha256 = sha256, + url = url, + **extra_args + ) + + if is_gt_bazel_7_1 and not block: + non_blocking.append(res) + return None + + if not res.success: + fail( + "Failed to download OCI object with digest {}\nStdout: {}\nStderr: {}".format( + digest, + res.stdout.strip(), + res.stderr.strip(), + ), + ) + + return struct( + path = outpath, + sha256 = sha256, + ) diff --git a/oci/private/repositories/oci_blob.bzl b/oci/private/repositories/oci_blob.bzl deleted file mode 100644 index 314ea03..0000000 --- a/oci/private/repositories/oci_blob.bzl +++ /dev/null @@ -1,56 +0,0 @@ -""" oci_blob """ - -load("@com_github_datadog_rules_oci//oci:providers.bzl", "OCIDescriptor") - -def _impl(ctx): - return [OCIDescriptor( - file = ctx.file.file, - media_type = ctx.attr.media_type, - size = ctx.attr.size, - urls = ctx.attr.urls, - digest = ctx.attr.digest, - annotations = ctx.attr.annotations, - )] - -oci_blob = rule( - implementation = _impl, - doc = """ -An internal rule to represent a blob in a content-addressable store. This -rule is usually generated by 'ocitool' when pulling an OCI artifact. - """, - provides = [OCIDescriptor], - attrs = { - "file": attr.label( - doc = """ -The blob stored as a file on disk. - """, - allow_single_file = True, - ), - "digest": attr.string( - doc = """ -A digest of the contents of the file, in the format '$ALGO:%HASH_IN_HEX', for -example 'sha256:abcd...' - """, - ), - "media_type": attr.string( - doc = """ -MIME type of blob. - """, - ), - "size": attr.int( - doc = """ -Size of content in bytes. - """, - ), - "urls": attr.string_list( - doc = """ -A list of URLs from which this object MAY be downloaded. - """, - ), - "annotations": attr.string_dict( - doc = """ -A map of arbitrary metadata relating to the targeted content. - """, - ), - }, -) diff --git a/oci/private/repositories/oci_image_index_manifest.bzl b/oci/private/repositories/oci_image_index_manifest.bzl deleted file mode 100644 index e0689a2..0000000 --- a/oci/private/repositories/oci_image_index_manifest.bzl +++ /dev/null @@ -1,30 +0,0 @@ -""" oci_image_index_manifest """ - -load( - "@com_github_datadog_rules_oci//oci:providers.bzl", - "OCIDescriptor", - "OCIImageIndexManifest", - "OCILayout", -) - -def _impl(ctx): - return [OCIImageIndexManifest( - manifests = [m[OCIDescriptor] for m in ctx.attr.manifests], - ), ctx.attr.layout[OCILayout], ctx.attr.descriptor[OCIDescriptor]] - -oci_image_index_manifest = rule( - implementation = _impl, - attrs = { - "descriptor": attr.label( - mandatory = True, - providers = [OCIDescriptor], - ), - "manifests": attr.label_list( - mandatory = False, - providers = [OCIDescriptor], - ), - "annotations": attr.string_dict(), - "layout": attr.label(), - }, - provides = [OCIImageIndexManifest], -) diff --git a/oci/private/repositories/oci_image_manifest.bzl b/oci/private/repositories/oci_image_manifest.bzl deleted file mode 100644 index 5e72957..0000000 --- a/oci/private/repositories/oci_image_manifest.bzl +++ /dev/null @@ -1,36 +0,0 @@ -""" oci_image_manifest """ - -load( - "@com_github_datadog_rules_oci//oci:providers.bzl", - "OCIDescriptor", - "OCIImageManifest", - "OCILayout", -) - -def _impl(ctx): - return [OCIImageManifest( - config = ctx.attr.config[OCIDescriptor], - layers = [layer[OCIDescriptor] for layer in ctx.attr.layers], - annotations = ctx.attr.annotations, - ), ctx.attr.layout[OCILayout], ctx.attr.descriptor[OCIDescriptor]] - -oci_image_manifest = rule( - implementation = _impl, - provides = [OCIImageManifest], - attrs = { - "descriptor": attr.label( - mandatory = True, - providers = [OCIDescriptor], - ), - "config": attr.label( - mandatory = True, - providers = [OCIDescriptor], - ), - "layers": attr.label_list( - mandatory = False, - providers = [OCIDescriptor], - ), - "annotations": attr.string_dict(), - "layout": attr.label(), - }, -) diff --git a/oci/private/repositories/oci_layout_index.bzl b/oci/private/repositories/oci_layout_index.bzl deleted file mode 100644 index fe44eeb..0000000 --- a/oci/private/repositories/oci_layout_index.bzl +++ /dev/null @@ -1,45 +0,0 @@ -""" oci_layout_index """ - -load("@com_github_datadog_rules_oci//oci:providers.bzl", "OCIDescriptor", "OCILayout") - -def _impl(ctx): - blobs_map = {} - all_files = [] - for blob in ctx.attr.blobs: - desc = blob[OCIDescriptor] - blobs_map[desc.digest] = desc.file.path - all_files.append(desc.file) - - obj = { - # TODO - #"index": ctx.attr.index[OCIDescriptor].file.path, - "blobs": blobs_map, - } - - ctx.actions.write( - output = ctx.outputs.json, - content = json.encode(obj), - ) - - return [ - OCILayout( - blob_index = ctx.outputs.json, - files = depset(all_files), - ), - ] - -oci_layout_index = rule( - implementation = _impl, - attrs = { - "index": attr.label( - providers = [OCIDescriptor], - ), - "blobs": attr.label_list( - providers = [OCIDescriptor], - ), - }, - outputs = { - "json": "%{name}.layout.json", - }, - provides = [OCILayout], -) diff --git a/oci/private/repositories/oci_pull.bzl b/oci/private/repositories/oci_pull.bzl index 36225c6..8155e19 100644 --- a/oci/private/repositories/oci_pull.bzl +++ b/oci/private/repositories/oci_pull.bzl @@ -1,139 +1,406 @@ """ oci_pull """ -# A directory to store cached OCI artifacts -# TODO(griffin) currently not used, but going to start depending on this for -# integration into the bzl wrapper. -OCI_CACHE_DIR_ENV = "OCI_CACHE_DIR" - -# XXX(griffin): quick hack to get Bazel to spit out debug info for oci_pull -DEBUG = False - -def failout(msg, cmd_result): - fail( - "{msg}\n stdout: {stdout} \n stderr: {stderr}" - .format(msg = msg, stdout = cmd_result.stdout, stderr = cmd_result.stderr), +load(":authn.bzl", _authn = "authn") +load( + ":download.bzl", + "MEDIA_TYPE_DOCKER_INDEX", + "MEDIA_TYPE_DOCKER_MANIFEST", + "MEDIA_TYPE_OCI_INDEX", + "MEDIA_TYPE_OCI_MANIFEST", + "download_blob", + "download_index_or_manifest", +) + +_SUPPORTED_PLATFORMS = [ + struct(arch = "amd64", os = "linux", variant = None), + struct(arch = "arm64", os = "linux", variant = "v8"), + struct(arch = "arm64", os = "linux", variant = None), +] + +def _impl(rctx): + non_blocking = [] # list of 'PendingDownload's + + authn = _authn.new(rctx, config_path = None) # authn object + + # Download the index or manifest. At this point, we do not know if the + # user provided an index or a amanifest. We will determine which it is by + # inspecting the downloaded file below + index_or_manifest = download_index_or_manifest( + rctx, + authn = authn, + digest = rctx.attr.digest, + outpath = "temp.json", + ) + + index_or_manifest_bytes = rctx.read(index_or_manifest.path) + index_or_manifest_json = json.decode(index_or_manifest_bytes) + rctx.delete(index_or_manifest.path) + + schema_version = index_or_manifest_json["schemaVersion"] + if schema_version != 2: + fail(""" +The registry sent a manifest with schemaVersion != 2. +This commonly occurs when fetching from a registry that needs the Docker-Distribution-API-Version header to be set. +See: https://github.com/bazel-contrib/rules_oci/blob/843eb01b152b884fe731a3fb4431b738ad00ea60/docs/pull.md#authentication-using-credential-helpers + """.strip()) + + media_type = index_or_manifest_json["mediaType"] + if media_type in [ + MEDIA_TYPE_DOCKER_INDEX, + MEDIA_TYPE_OCI_INDEX, + ]: + _handle_index( + rctx, + authn = authn, + index_json = index_or_manifest_json, + non_blocking = non_blocking, + ) + elif media_type in [ + MEDIA_TYPE_DOCKER_MANIFEST, + MEDIA_TYPE_OCI_MANIFEST, + ]: + _handle_manifest( + rctx, + authn = authn, + manifest_bytes = index_or_manifest_bytes, + manifest_json = index_or_manifest_json, + manifest_sha256 = index_or_manifest.sha256, + non_blocking = non_blocking, + ) + else: + fail( + """ +oci_pull can only be used to pull an image index or an image manifest. +Got media type: {media_type}. +Expected one of: {expected}. + """.strip().foramt( + media_type = media_type, + expected = [ + MEDIA_TYPE_DOCKER_INDEX, + MEDIA_TYPE_DOCKER_MANIFEST, + MEDIA_TYPE_OCI_INDEX, + MEDIA_TYPE_OCI_MANIFEST, + ], + ), + ) + + rctx.file( + rctx.path("oci-layout"), + content = json.encode({"imageLayoutVersion": "1.0.0"}), + executable = False, ) -# buildifier: disable=function-docstring -def pull(rctx, layout_root, repository, digest, registry = "", shallow = False, debug = False): - cmd = [ - rctx.path(_repo_toolchain(rctx, "ocitool")), + # Wait for all downloads to complete + for result in non_blocking: + result.wait() + + # Get the filenames of all the files in the blobs/sha256 directory + res = rctx.execute( + ["find", ".", "-maxdepth", "1", "-type", "f"], + working_directory = "blobs/sha256", + ) + blobs = [ + s.removeprefix("./") + for s in res.stdout.strip().split("\n") ] - if debug: - cmd.append("--debug") - - cmd.extend([ - "--layout={layout_root}".format(layout_root = layout_root), - "pull", - "--shallow={shallow}".format(shallow = shallow), - "{registry}/{repository}@{digest}".format( - registry = registry, - repository = repository, - digest = digest, + # Create all the BUILD.bazel files + + rctx.file( + rctx.path("BUILD.bazel"), + content = """ +exports_files( + ["index.json", "oci-layout"], + visibility = ["//:__subpackages__"], +) +""".strip(), + executable = False, + ) + + rctx.file( + rctx.path("image/BUILD.bazel"), + content = """ +load( + "@com_github_datadog_rules_oci//oci/private/repositories:oci_pulled_image.bzl", + "oci_pulled_image", +) +load("@rules_pkg//pkg:pkg.bzl", "pkg_tar") + +oci_pulled_image( + name = "image", + index = "//:index.json", + blobs = [ +{blobs} + ], + visibility = ["//visibility:public"], +) + +pkg_tar( + name = "tar", + srcs = [ + "//:index.json", + "//:oci-layout", + "//blobs/sha256:blobs", + ], + extension = "tar.gz", + package_file_name = "image.tar.gz", + tags = ["manual"], + visibility = ["//visibility:public"], +) +""".strip().format( + blobs = ",\n".join([ + ' "//blobs/sha256:{}\"'.format(blob) + for blob in blobs + ]), ), - ]) - - res = rctx.execute(cmd, quiet = not DEBUG) - if res.return_code > 0: - failout("failed to pull manifest", res) - -def generate_build_files(rctx, layout_root, digest = ""): - cmd = [ - rctx.path(_repo_toolchain(rctx, "ocitool")), - "--debug", - "--layout={layout_root}".format(layout_root = layout_root), - "generate-build-files", - "--image-digest={digest}".format(digest = digest), - ] + executable = False, + ) - res = rctx.execute(cmd, quiet = not DEBUG) - if res.return_code > 0: - failout("failed to pull manifest", res) + rctx.file( + rctx.path("blobs/sha256/BUILD.bazel"), + content = """ +load("@rules_pkg//pkg:mappings.bzl", "pkg_files") -def _impl(rctx): - pull( +exports_files( + glob(["**/*"]), + visibility = ["//:__subpackages__"], +) + +pkg_files( + name = "blobs", + srcs = glob(["**/*"]), + prefix = "blobs/sha256", + visibility = ["//:__subpackages__"], +) +""".strip(), + executable = False, + ) + +def _handle_index( rctx, - rctx.path("."), - repository = rctx.attr.repository, - digest = rctx.attr.digest, - registry = rctx.attr.registry, - shallow = rctx.attr.shallow, + *, + authn, # authn object + index_json, # dict[str, any] + non_blocking): # list[PendingDownload] + # -> None + + # Filter out unsupported platforms + original_manifest_jsons = index_json["manifests"][:] + index_json["manifests"] = [] + for manifest_json in original_manifest_jsons: + platform = struct( + arch = manifest_json["platform"]["architecture"], + os = manifest_json["platform"]["os"], + variant = manifest_json["platform"].get("variant", None), + ) + if platform not in _SUPPORTED_PLATFORMS: + continue + index_json["manifests"].append(manifest_json) + + index_bytes = json.encode(index_json) + + # Write the index to index.json + index_path = "index.json" + rctx.file(index_path, content = index_bytes, executable = False) + index_sha256 = _sha256sum(rctx, path = index_path) + + # Write the index to the blobs/sha256 directory + rctx.file( + "blobs/sha256/{}".format(index_sha256), + content = index_bytes, + executable = False, ) - generate_build_files( + # For each manifest + for item in index_json["manifests"]: + # Download the manifest + manifest_digest = item["digest"] + manifest_sha256 = manifest_digest[len("sha256:"):] + manifest = download_index_or_manifest( + rctx, + authn = authn, + digest = manifest_digest, + outpath = "blobs/sha256/{}".format(manifest_sha256), + ) + manifest_bytes = rctx.read(manifest.path) + manifest_json = json.decode(manifest_bytes) + + # Download the config + config_digest = manifest_json["config"]["digest"] + download_blob( + rctx, + authn = authn, + digest = config_digest, + non_blocking = non_blocking, + ) + + # Download the layers + for item in manifest_json["layers"]: + layer_digest = item["digest"] + download_blob( + rctx, + authn = authn, + digest = layer_digest, + non_blocking = non_blocking, + ) + +def _handle_manifest( rctx, - rctx.path("."), - digest = rctx.attr.digest, + *, + authn, # authn object + manifest_bytes, # bytes + manifest_json, # dict[str, any] + manifest_sha256, # str + non_blocking): # list[PendingDownload] + # -> None + + # Write the manifest to the blobs directory + rctx.file( + "blobs/sha256/{}".format(manifest_sha256), + content = manifest_bytes, + executable = False, + ) + + # Download the config + config_digest = manifest_json["config"]["digest"] + config = download_blob(rctx, authn = authn, digest = config_digest) + config_bytes = rctx.read(config.path) + config_json = json.decode(config_bytes) + + # Read the config for information about the arch/os/variant + platform = struct( + arch = config_json["architecture"], + os = config_json["os"], + variant = config_json.get("variant", None), + ) + + # Error on unsupported platforms + if platform not in _SUPPORTED_PLATFORMS: + fail( + """ +Unsupported platform. Got {}. Expected one of: {supported_platforms} +""".strip().format( + platform = platform, + supported_platforms = _SUPPORTED_PLATFORMS, + ), + ) + + # Create an index and write it to index.json + index_json = { + "manifests": [{ + "digest": "sha256:{}".format(manifest_sha256), + "mediaType": MEDIA_TYPE_OCI_MANIFEST, + "platform": { + "architecture": platform.arch, + "os": platform.os, + }, + "size": len(manifest_bytes), + }], + "mediaType": MEDIA_TYPE_OCI_INDEX, + "schemaVersion": 2, + } + if platform.variant != None: + index_json["manifests"][0]["platform"]["variant"] = platform.variant + + index_path = "index.json" + index_bytes = json.encode(index_json) + rctx.file(index_path, content = index_bytes, executable = False) + index_sha256 = _sha256sum(rctx, path = index_path) + + # Write the index to the blobs/sha256 directory + rctx.file( + "blobs/sha256/{}".format(index_sha256), + content = index_bytes, + executable = False, + ) + + # Download the layers + for item in manifest_json["layers"]: + layer_digest = item["digest"] + download_blob( + rctx, + authn = authn, + digest = layer_digest, + non_blocking = non_blocking, + ) + +def _sha256sum( + rctx, + *, + path): # str + # -> str + temp_exe = "sha256-temp.sh" + rctx.file( + temp_exe, + executable = True, + content = """ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -ne 1 ]]; then + echo "Wrong number of arguments" >&2 + echo "Usage: $0 " >&2 + exit 1 +fi + +path="$1" + +os="$(uname -s)" +case "$os" in + Linux) sha256sum "$path" | cut -d ' ' -f 1 ;; + Darwin) shasum -a 256 "$path" | cut -d ' ' -f 1 ;; + *) echo "Unsupported OS. Got $os. Expected of one Darwin or Linux" >&2 ; exit 1 ;; +esac +""".strip().format( + path = path, + ), ) + res = rctx.execute(["./" + temp_exe, path]) + if res.return_code != 0: + fail( + """ +Failed to calculate the sha256 of index.json +Exit code: {exit_code} +Stdout: {stdout} +Stderr: {stderr} +""".strip().format( + exit_code = res.return_code, + stdout = res.stdout.strip(), + stderr = res.stderr.strip(), + ), + ) + rctx.delete(temp_exe) + sha256 = res.stdout.strip().split(" ")[0] + return sha256 oci_pull = repository_rule( implementation = _impl, - doc = """ - """, attrs = { - "registry": attr.string( + "debug": attr.bool( + # TODO(brian.myers): Remove this once consumers no longer use it + doc = "Deprecated. Does nothing", + ), + "digest": attr.string( + doc = "The digest or tag of the manifest file", mandatory = True, - doc = """ - """, ), - "repository": attr.string( + "registry": attr.string( + doc = "Remote registry host to pull from, e.g. `gcr.io` or `index.docker.io`", mandatory = True, - doc = """ - """, ), - # XXX We're specifically *NOT* supporting pulling by tags as it's - # difficult for users to control when the tag resolution is done. - "digest": attr.string( + "repository": attr.string( + doc = "Image path beneath the registry, e.g. `distroless/static`", mandatory = True, - doc = """ - """, ), - "debug": attr.bool( - default = False, - doc = "Enable ocitool debug output", + "scheme": attr.string( + doc = "scheme portion of the URL for fetching from the registry", + values = ["http", "https"], + default = "https", ), "shallow": attr.bool( - default = True, - doc = """ - """, - ), - "_ocitool_darwin_amd64": attr.label( - default = "@com_github_datadog_rules_oci//bin:ocitool-darwin-amd64", - ), - "_ocitool_darwin_arm64": attr.label( - default = "@com_github_datadog_rules_oci//bin:ocitool-darwin-arm64", - ), - "_ocitool_linux_amd64": attr.label( - default = "@com_github_datadog_rules_oci//bin:ocitool-linux-amd64", - ), - "_ocitool_linux_arm64": attr.label( - default = "@com_github_datadog_rules_oci//bin:ocitool-linux-arm64", + # TODO(brian.myers): Remove this once consumers no longer use it + doc = "Deprecated. Does nothing", ), }, - environ = [ - OCI_CACHE_DIR_ENV, - ], + environ = _authn.ENVIRON, ) - -def _repo_toolchain(rctx, tool_name): - goos = "" - goarch = "" - - if rctx.os.name.lower().startswith("mac os"): - goos = "darwin" - elif rctx.os.name.lower().startswith("linux"): - goos = "linux" - else: - fail("unknown os: {}".format(rctx.os.name)) - - # TODO update to use rctx.os.arch when released - arch = rctx.execute(["uname", "-m"]).stdout.strip() - if arch.lower().find("x86") != -1: - goarch = "amd64" - elif arch.lower().find("arm64") != -1 or arch.lower().find("aarch64") != -1: - goarch = "arm64" - else: - fail("unknown arch: {}".format(rctx.os.arch)) - - return getattr(rctx.attr, "_{tool}_{os}_{arch}".format(tool = tool_name, os = goos, arch = goarch)) diff --git a/oci/private/repositories/oci_pulled_image.bzl b/oci/private/repositories/oci_pulled_image.bzl new file mode 100644 index 0000000..050a91e --- /dev/null +++ b/oci/private/repositories/oci_pulled_image.bzl @@ -0,0 +1,125 @@ +""" oci_pulled_image """ + +load("@com_github_datadog_rules_oci//oci:providers.bzl", "OCIDescriptor", "OCILayout") +load(":download.bzl", "MEDIA_TYPE_OCI_INDEX") + +_COREUTILS_TOOLCHAIN = "@aspect_bazel_lib//lib:coreutils_toolchain_type" + +def oci_pulled_image( + *, + name, + index, # label + blobs, # list[label] + **kwargs): + """oci_pulled_image + + Args: + name: A unique name for this rule + index: The OCI index.json file + blobs: A list of the blob files + **kwargs: Additional arguments to pass to the rule, e.g. tags or visibility + """ + _oci_pulled_image( + name = name, + index = index, + blobs = blobs, + **kwargs + ) + +def _impl(ctx): + coreutils = ctx.toolchains[_COREUTILS_TOOLCHAIN].coreutils_info.bin + + # Create the descriptor file for the index.json of the image + descriptor_file = ctx.actions.declare_file("{}.index.descriptor.json".format(ctx.label.name)) + ctx.actions.run_shell( + inputs = [ctx.file.index], + outputs = [descriptor_file], + tools = [coreutils], + command = """ +#!/usr/bin/env bash +set -euo pipefail + +coreutils={coreutils} +inpath={inpath} +media_type={media_type} +outpath={outpath} + +sha256=$($coreutils sha256sum $inpath | $coreutils cut -d ' ' -f 1) +size=$($coreutils wc -c $inpath | $coreutils cut -d ' ' -f 1) + +cat < $outpath +{{ + "digest": "sha256:$sha256", + "mediaType": "$media_type", + "size": $size +}} +EOF +""".strip().format( + coreutils = coreutils.path, + inpath = ctx.file.index.path, + media_type = MEDIA_TYPE_OCI_INDEX, + outpath = descriptor_file.path, + ), + ) + + # Create the "blob index" file (also called a "layout" file) for the image + blob_index = ctx.actions.declare_file("{}.index.layout.json".format(ctx.label.name)) + ctx.actions.run_shell( + inputs = ctx.files.blobs, + outputs = [blob_index], + command = """ +#!/usr/bin/env bash +set -euo pipefail + +blobs=({blobs}) +outpath={outpath} + +cat < $outpath +{{ + "blobs": {{ +EOF + +first=1 # true=1 and false=0 +for blob in ${{blobs[@]}}; do + sha256=$(basename $blob) + if [[ $first -eq 1 ]]; then + first=0 + else + echo "," >> $outpath + fi + echo -n " \\"sha256:$sha256\\": \\"$blob\\"" >> $outpath +done + +cat <> $outpath + + }} +}} +EOF +""".strip().format( + blobs = " ".join([f.path for f in ctx.files.blobs]), + outpath = blob_index.path, + ), + ) + + return [ + DefaultInfo( + files = depset([descriptor_file, blob_index]), + ), + OCIDescriptor( + descriptor_file = descriptor_file, + file = ctx.file.index, + ), + OCILayout( + blob_index = blob_index, + files = depset(ctx.files.blobs), + ), + ] + +_oci_pulled_image = rule( + implementation = _impl, + attrs = { + "blobs": attr.label_list(allow_files = True, mandatory = True), + "index": attr.label(allow_single_file = True, mandatory = True), + }, + toolchains = [_COREUTILS_TOOLCHAIN], +) diff --git a/oci/providers.bzl b/oci/providers.bzl index d22ff88..820e955 100644 --- a/oci/providers.bzl +++ b/oci/providers.bzl @@ -1,25 +1,5 @@ """ public providers """ -OCIReferenceInfo = provider( - doc = "Refers to any artifact represented by an OCI-like reference URI", - fields = { - "registry": "the URI where the artifact is stored", - "repository": "a namespace for an artifact", - "tag": "a organizational reference within a repository", - "tag_file": "a file containing the organizational reference within a repository", - "digest": "a file containing the digest of the artifact", - }, -) - -# buildifier: disable=name-conventions -OCILayout = provider( - "OCI Layout", - fields = { - "blob_index": "", - "files": "", - }, -) - # buildifier: disable=name-conventions OCIDescriptor = provider( doc = "", @@ -35,27 +15,21 @@ OCIDescriptor = provider( ) # buildifier: disable=name-conventions -OCIImageManifest = provider( - doc = "", - fields = { - "config": "Descriptor that points to a configuration object", - "layers": "List of descriptors", - "annotations": "String map of arbitrary metadata", - }, -) - -# buildifier: disable=name-conventions -OCIImageIndexManifest = provider( - doc = "", +OCILayout = provider( + "OCI Layout", fields = { - "manifests": "List of descriptors", - "annotations": "String map of arbitrary metadata", + "blob_index": "", + "files": "", }, ) -OCISDK = provider( - "The OCI SDK", +OCIReferenceInfo = provider( + doc = "Refers to any artifact represented by an OCI-like reference URI", fields = { - "ocitool": "", + "registry": "the URI where the artifact is stored", + "repository": "a namespace for an artifact", + "tag": "a organizational reference within a repository", + "tag_file": "a file containing the organizational reference within a repository", + "digest": "a file containing the digest of the artifact", }, ) diff --git a/oci/toolchain.bzl b/oci/toolchain.bzl index d8811a9..a671a11 100644 --- a/oci/toolchain.bzl +++ b/oci/toolchain.bzl @@ -1,110 +1,3 @@ -""" toolchain """ - -load(":providers.bzl", "OCISDK") - -# Follow golang's conventions for naming os and arch -OS_CONSTRAINTS = { - "darwin": "@platforms//os:osx", - "linux": "@platforms//os:linux", -} - -ARCH_CONSTRAINTS = { - "amd64": "@platforms//cpu:x86_64", - "arm64": "@platforms//cpu:arm64", -} - -def _oci_toolchain_impl(ctx): - return [platform_common.ToolchainInfo( - sdk = ctx.attr.sdk[OCISDK], - )] - -_oci_toolchain = rule( - implementation = _oci_toolchain_impl, - attrs = { - "sdk": attr.label( - mandatory = True, - providers = [OCISDK], - cfg = "exec", - ), - }, - provides = [platform_common.ToolchainInfo], -) - -def oci_toolchain( - name, - exec_compatible_with = [], - target_compatible_with = [], - **kwargs): - oci_toolchain_name = "{name}.oci_toolchain".format(name = name) - _oci_toolchain( - name = oci_toolchain_name, - **kwargs - ) - - native.toolchain( - name = name, - toolchain = oci_toolchain_name, - exec_compatible_with = exec_compatible_with, - target_compatible_with = target_compatible_with, - toolchain_type = "@com_github_datadog_rules_oci//oci:toolchain", - ) - -def _oci_sdk_impl(ctx): - return [ - OCISDK( - ocitool = ctx.executable.ocitool, - ), - ] - -oci_sdk = rule( - implementation = _oci_sdk_impl, - attrs = { - "ocitool": attr.label( - mandatory = True, - allow_single_file = True, - executable = True, - cfg = "exec", - ), - }, - provides = [OCISDK], -) - -def oci_local_toolchain(name, **kwargs): - sdk_name = "{}.sdk".format(name) - oci_sdk( - name = sdk_name, - ocitool = "@com_github_datadog_rules_oci//go/cmd/ocitool", - ) - - oci_toolchain( - name = name, - sdk = sdk_name, - **kwargs - ) - -# buildifier: disable=function-docstring -def create_compiled_oci_toolchains(name, **kwargs): - for os, os_const in OS_CONSTRAINTS.items(): - for arch, arch_const in ARCH_CONSTRAINTS.items(): - sdk_name = "{name}_sdk_{os}_{arch}".format(name = name, os = os, arch = arch) - oci_sdk( - name = sdk_name, - ocitool = "@com_github_datadog_rules_oci//bin:ocitool-{os}-{arch}".format(os = os, arch = arch), - ) - - toolchain_name = "{name}_toolchain_{os}_{arch}".format(name = name, os = os, arch = arch) - oci_toolchain( - name = toolchain_name, - sdk = sdk_name, - exec_compatible_with = [ - os_const, - arch_const, - ], - **kwargs - ) - -def register_compiled_oci_toolchains(name): - for os, _ in OS_CONSTRAINTS.items(): - for arch, _ in ARCH_CONSTRAINTS.items(): - toolchain_name = "{name}_toolchain_{os}_{arch}".format(name = name, os = os, arch = arch) - native.register_toolchains("@com_github_datadog_rules_oci//bin:{}".format(toolchain_name)) +# TODO(brian.myers): Remove this once consumers no longer use it +def register_compiled_oci_toolchains(**kwargs): + pass diff --git a/release/BUILD.bazel b/release/BUILD.bazel index b5a81dd..851588a 100644 --- a/release/BUILD.bazel +++ b/release/BUILD.bazel @@ -1,11 +1,8 @@ load("@rules_pkg//pkg:pkg.bzl", "pkg_tar") -load(":pkg_go_binary_multiple_platforms.bzl", "pkg_go_binary_multiple_platforms") pkg_tar( name = "release", srcs = [ - ":ocitool", - "//bin:files", "//oci:files", "//oci/private:files", "//oci/private/repositories:files", @@ -13,10 +10,3 @@ pkg_tar( empty_files = ["BUILD.bazel"], extension = "tar.gz", ) - -pkg_go_binary_multiple_platforms( - name = "ocitool", - go_binary = ["//go/cmd/ocitool:go_default_library"], - mode = "0755", - prefix = "bin", -) diff --git a/release/README.md b/release/README.md index 50ba96d..fbd4ba5 100644 --- a/release/README.md +++ b/release/README.md @@ -11,8 +11,8 @@ bzl run //go/cmd/ocitool -- push-blob --ref "ghcr.io/datadog/rules_oci/rules:lat ## Updating Licenses and Headers ``` -go install github.com/DataDog/temporalite/internal/licensecheck@latest -go install github.com/DataDog/temporalite/internal/copyright@latest +bzl run //:go -- install github.com/DataDog/temporalite/internal/licensecheck@latest +bzl run //:go -- install github.com/DataDog/temporalite/internal/copyright@latest licensecheck copyright ``` diff --git a/release/pkg_go_binary_multiple_platforms.bzl b/release/pkg_go_binary_multiple_platforms.bzl deleted file mode 100644 index 4b4ded6..0000000 --- a/release/pkg_go_binary_multiple_platforms.bzl +++ /dev/null @@ -1,59 +0,0 @@ -""" pkg_go_binary_multiple_platforms """ - -load("@io_bazel_rules_go//go:def.bzl", _go_binary = "go_binary") -load("@rules_pkg//pkg:mappings.bzl", "pkg_attributes", "pkg_files") - -_GOOSS = ["darwin", "linux"] -_GOARCHS = ["amd64", "arm64"] - -# buildifier: disable=function-docstring -def pkg_go_binary_multiple_platforms( - *, - name, # str - go_binary, # label - mode, # str - prefix, # str | None - **kwargs): - """ pkg_go_binary_multiple_platforms - - Creates a pkg_files target called that includes multiple go binary - executables (one for each platform) based off the provided go binary target. - - In other words, the following 4 exectuable will be included in the pkg_files - target called : - - -darwin-amd64 - - -darwin-arm64 - - -linux-amd64 - - -linux-arm64 - - Args: - name: - - The name of the pkg_files target to create - - Also determines the names of the executables insde the pkg_files - target, which will be of the form "--". - go_binary: The go_binary target to embed multiple versions of - mode: The mode of the executables in pkg_files - prefix: The prefix to pass to pkg_files - **kwargs: Additional arguments to pass to pkg_files - """ - all_binaries = [] - for goos in _GOOSS: - for goarch in _GOARCHS: - bin_name = "{}-{}-{}".format(name, goos, goarch) - _go_binary( - name = bin_name, - embed = go_binary, - goos = goos, - goarch = goarch, - ) - all_binaries.append(bin_name) - - pkg_files( - name = name, - srcs = all_binaries, - attributes = pkg_attributes( - mode = mode, - ), - prefix = prefix, - **kwargs - )