diff --git a/.bazelrc b/.bazelrc index 3ce91d27..3c382986 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1 +1,7 @@ common --enable_bzlmod + +# The go_deps extension is isolated so the docker proto-disable gazelle_override +# (forbidden in non-root modules) keeps working when bazel/test consumes this +# module as a dependency to build the from-source container-structure-test +# binary. Isolated usages may carry overrides regardless of root status. +common --experimental_isolated_extension_usages diff --git a/.github/workflows/bazel.yaml b/.github/workflows/bazel.yaml index e87382a6..7a9e5450 100644 --- a/.github/workflows/bazel.yaml +++ b/.github/workflows/bazel.yaml @@ -10,7 +10,7 @@ on: jobs: test: - uses: bazel-contrib/.github/.github/workflows/bazel.yaml@v7 + uses: bazel-contrib/.github/.github/workflows/bazel.yaml@v7.7.0 with: folders: '["bazel/test"]' exclude_windows: true diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 54ffbd1c..daa9bdd9 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -1,47 +1,76 @@ name: PR unit tests -# Triggers the workflow on push or pull request events -on: [push, pull_request] +# PRs are covered by pull_request; push is limited to main so same-repo PR +# branches don't run every build twice. +on: + push: + branches: [main] + pull_request: permissions: read-all concurrency: - # On master/release, we don't want any jobs cancelled + # On main, we don't want any jobs cancelled # On PR branches, we cancel the job if new commits are pushed # More info: https://stackoverflow.com/a/70972844/1261287 group: ${{ github.ref }} - cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: build: name: PR unit tests strategy: matrix: - os: [ubuntu-latest, macos-12] + # macos-15-intel is the only GitHub-hosted macOS runner that can run a + # Docker daemon: colima needs nested virtualization, which is absent on + # Apple Silicon runners (HV_UNSUPPORTED) and gone from retired macos-12/13. + os: [ubuntu-latest, macos-15-intel] fail-fast: false runs-on: ${{ matrix.os }} - + steps: - - name: start docker - if: ${{ matrix.os == 'macos-12' }} - run: | - brew install docker - colima start - echo "DOCKER_HOST=unix:///Users/runner/.colima/default/docker.sock" >> $GITHUB_ENV - + # Installs colima + the Docker CLI and starts the daemon; sets the active + # docker context so `docker` works without DOCKER_HOST. macOS-only — the + # action's own ARM64 guard exits if it ever lands on an Apple Silicon runner. + - name: Set up Docker (macOS) + if: ${{ runner.os == 'macOS' }} + uses: douglascamata/setup-docker-macos-action@d5ccc6aae0ce23e7700154f5e63cc53e6433ac48 # v1.1.0 + + - name: Point go-dockerclient at colima's socket (macOS) + if: ${{ runner.os == 'macOS' }} + # The container-structure-test binary uses go-dockerclient, which honors + # DOCKER_HOST but NOT the docker CLI context that colima configures. Without + # this it falls back to /var/run/docker.sock (absent on colima) and every + # docker-driver test fails. Derive the socket from colima's docker context. + run: echo "DOCKER_HOST=$(docker context inspect colima -f '{{.Endpoints.docker.Host}}')" >> "$GITHUB_ENV" + - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0 with: - go-version: ^1.22 + go-version: ^1.24 id: go - - - uses: imjasonh/setup-crane@v0.4 + + - uses: imjasonh/setup-crane@59c71e96a00b28651f10369ba3359a6d730740a0 # v0.6 - name: Check out code into the Go module directory - uses: actions/checkout@v4 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: fetch-depth: 0 + persist-credentials: false - name: Run tests run: | make test + + # Ensure the Go code stays buildable and testable via Bazel (rules_go + gazelle). + # Scoped to the Go packages: //bazel/test is the rule's smoke test (covered by + # the dedicated Bazel workflow as its own module). + - name: Bazel + if: ${{ matrix.os == 'ubuntu-latest' }} + run: | + bazel build //cmd/... //pkg/... //internal/... //testutil/... + bazel test //pkg/... //internal/... + # Doc-freshness: //bazel/docs:update_test fails if bazel/docs/defs.md + # drifts from the rule. Run `bazel run //bazel/docs:update` to regenerate. + bazel build //bazel/docs/... + bazel test //bazel/docs:update_test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ec7dabeb..e2e0259b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,15 +13,15 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: go-version-file: 'go.mod' - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v6 + uses: goreleaser/goreleaser-action@v7 with: distribution: goreleaser version: latest diff --git a/BUILD.bazel b/BUILD.bazel index 26a7e828..4017ba80 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,4 +1,9 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +load("@gazelle//:def.bzl", "gazelle") + +# gazelle:prefix github.com/GoogleContainerTools/container-structure-test +# gazelle:exclude bazel +gazelle(name = "gazelle") # For stardoc to reference the file directly exports_files(["defs.bzl"]) @@ -6,6 +11,6 @@ exports_files(["defs.bzl"]) bzl_library( name = "defs", srcs = ["defs.bzl"], - deps = ["//bazel:container_structure_test"], visibility = ["//visibility:public"], + deps = ["//bazel:container_structure_test"], ) diff --git a/MODULE.bazel b/MODULE.bazel index d0a3990a..47373661 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -29,3 +29,31 @@ register_toolchains("@structure_test_toolchains//:all") # 0.5.4 is the first version with bzlmod support bazel_dep(name = "stardoc", version = "0.7.2", dev_dependency = True, repo_name = "io_bazel_stardoc") + +bazel_dep(name = "rules_go", version = "0.61.1", repo_name = "io_bazel_rules_go") +bazel_dep(name = "gazelle", version = "0.51.3") + +# go-dockerclient v1.13.2 declares `go 1.25.5`; pin the SDK so bazel's go_deps +# fetch/build satisfies that directive (rules_go 0.61.1 otherwise defaults to +# go 1.25.0, which fails with "requires go >= 1.25.5"). +go_sdk = use_extension("@io_bazel_rules_go//go:extensions.bzl", "go_sdk") +go_sdk.download(version = "1.25.5") + +go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps", isolate = True) +go_deps.from_file(go_mod = "//:go.mod") + +# All *direct* Go dependencies of the module have to be listed explicitly. +use_repo( + go_deps, + "com_github_fsouza_go_dockerclient", + "com_github_google_go_cmp", + "com_github_google_go_containerregistry", + "com_github_joho_godotenv", + "com_github_moby_sys_sequential", + "com_github_opencontainers_image_spec", + "com_github_pkg_errors", + "com_github_sirupsen_logrus", + "com_github_spf13_cobra", + "in_gopkg_yaml_v2", + "org_golang_x_crypto", +) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index c0912021..6938c513 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -25,6 +25,7 @@ "https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.5/MODULE.bazel": "004ba890363d05372a97248c37205ae64b6fa31047629cd2c0895a9d0c7779e8", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.22.5/source.json": "ac2c3213df8f985785f1d0aeb7f0f73d5324e6e67d593d9b9470fb74a25d4a9b", "https://bcr.bazel.build/modules/aspect_bazel_lib/2.8.1/MODULE.bazel": "812d2dd42f65dca362152101fbec418029cc8fd34cbad1a2fde905383d705838", + "https://bcr.bazel.build/modules/bazel_features/1.1.0/MODULE.bazel": "cfd42ff3b815a5f39554d97182657f8c4b9719568eb7fded2b9135f084bf760b", "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", "https://bcr.bazel.build/modules/bazel_features/1.10.0/MODULE.bazel": "f75e8807570484a99be90abcd52b5e1f390362c258bcb73106f4544957a48101", "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", @@ -39,8 +40,10 @@ "https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9", "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", "https://bcr.bazel.build/modules/bazel_features/1.33.0/MODULE.bazel": "8b8dc9d2a4c88609409c3191165bccec0e4cb044cd7a72ccbe826583303459f6", - "https://bcr.bazel.build/modules/bazel_features/1.33.0/source.json": "13617db3930328c2cd2807a0f13d52ca870ac05f96db9668655113265147b2a6", + "https://bcr.bazel.build/modules/bazel_features/1.36.0/MODULE.bazel": "596cb62090b039caf1cad1d52a8bc35cf188ca9a4e279a828005e7ee49a1bec3", "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", + "https://bcr.bazel.build/modules/bazel_features/1.42.1/MODULE.bazel": "275a59b5406ff18c01739860aa70ad7ccb3cfb474579411decca11c93b951080", + "https://bcr.bazel.build/modules/bazel_features/1.42.1/source.json": "fcd4396b2df85f64f2b3bb436ad870793ecf39180f1d796f913cc9276d355309", "https://bcr.bazel.build/modules/bazel_features/1.9.0/MODULE.bazel": "885151d58d90d8d9c811eb75e3288c11f850e1d6b481a8c9f766adee4712358b", "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", "https://bcr.bazel.build/modules/bazel_lib/3.0.0/MODULE.bazel": "22b70b80ac89ad3f3772526cd9feee2fa412c2b01933fea7ed13238a448d370d", @@ -59,10 +62,16 @@ "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6", "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67", "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/source.json": "34a3c8bcf233b835eb74be9d628899bb32999d3e0eadef1947a0a562a2b16ffb", - "https://bcr.bazel.build/modules/buildozer/8.2.1/MODULE.bazel": "61e9433c574c2bd9519cad7fa66b9c1d2b8e8d5f3ae5d6528a2c2d26e68d874d", - "https://bcr.bazel.build/modules/buildozer/8.2.1/source.json": "7c33f6a26ee0216f85544b4bca5e9044579e0219b6898dd653f5fb449cf2e484", + "https://bcr.bazel.build/modules/buildozer/8.5.1/MODULE.bazel": "a35d9561b3fc5b18797c330793e99e3b834a473d5fbd3d7d7634aafc9bdb6f8f", + "https://bcr.bazel.build/modules/buildozer/8.5.1/source.json": "e3386e6ff4529f2442800dee47ad28d3e6487f36a1f75ae39ae56c70f0cd2fbd", "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/MODULE.bazel": "cdf8cbe5ee750db04b78878c9633cc76e80dcf4416cbe982ac3a9222f80713c8", "https://bcr.bazel.build/modules/gawk/5.3.2.bcr.1/source.json": "fa7b512dfcb5eafd90ce3959cf42a2a6fe96144ebbb4b3b3928054895f2afac2", + "https://bcr.bazel.build/modules/gazelle/0.32.0/MODULE.bazel": "b499f58a5d0d3537f3cf5b76d8ada18242f64ec474d8391247438bf04f58c7b8", + "https://bcr.bazel.build/modules/gazelle/0.33.0/MODULE.bazel": "a13a0f279b462b784fb8dd52a4074526c4a2afe70e114c7d09066097a46b3350", + "https://bcr.bazel.build/modules/gazelle/0.34.0/MODULE.bazel": "abdd8ce4d70978933209db92e436deb3a8b737859e9354fb5fd11fb5c2004c8a", + "https://bcr.bazel.build/modules/gazelle/0.36.0/MODULE.bazel": "e375d5d6e9a6ca59b0cb38b0540bc9a05b6aa926d322f2de268ad267a2ee74c0", + "https://bcr.bazel.build/modules/gazelle/0.51.3/MODULE.bazel": "618a729142f66de1e2cb776d026413763be5b80d5e3d29ffb9d3d90c5defde90", + "https://bcr.bazel.build/modules/gazelle/0.51.3/source.json": "fbe5312a01fb4a2a58caff4a0d40dcf384b6f0bda75e85546663360da1d7538a", "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", @@ -79,7 +88,9 @@ "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/MODULE.bazel": "6f7b417dcc794d9add9e556673ad25cb3ba835224290f4f848f8e2db1e1fca74", "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/source.json": "f448c6e8963fdfa7eb831457df83ad63d3d6355018f6574fb017e8169deb43a9", "https://bcr.bazel.build/modules/package_metadata/0.0.2/MODULE.bazel": "fb8d25550742674d63d7b250063d4580ca530499f045d70748b1b142081ebb92", - "https://bcr.bazel.build/modules/package_metadata/0.0.2/source.json": "e53a759a72488d2c0576f57491ef2da0cf4aab05ac0997314012495935531b73", + "https://bcr.bazel.build/modules/package_metadata/0.0.3/MODULE.bazel": "77890552ecea9e284b5424c9de827a58099348763a4359e975c359a83d4faa83", + "https://bcr.bazel.build/modules/package_metadata/0.0.5/MODULE.bazel": "ef4f9439e3270fdd6b9fd4dbc3d2f29d13888e44c529a1b243f7a31dfbc2e8e4", + "https://bcr.bazel.build/modules/package_metadata/0.0.5/source.json": "2326db2f6592578177751c3e1f74786b79382cd6008834c9d01ec865b9126a85", "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", @@ -89,13 +100,19 @@ "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", "https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc", "https://bcr.bazel.build/modules/platforms/1.0.0/MODULE.bazel": "f05feb42b48f1b3c225e4ccf351f367be0371411a803198ec34a389fb22aa580", - "https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96", + "https://bcr.bazel.build/modules/platforms/1.1.0/MODULE.bazel": "1c0c09f5bdcf4b3f924720d2478a3711cb39f4977019ca5988685e5b7e18b3d2", + "https://bcr.bazel.build/modules/platforms/1.1.0/source.json": "fcf351c47596c939140ab0d333dfdd08ed1ea6ce33c2fe70c12493a301cf1344", "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", + "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", + "https://bcr.bazel.build/modules/protobuf/29.0-rc2.bcr.1/MODULE.bazel": "52f4126f63a2f0bbf36b99c2a87648f08467a4eaf92ba726bc7d6a500bbf770c", "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", "https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92", + "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", "https://bcr.bazel.build/modules/protobuf/29.1/MODULE.bazel": "557c3457560ff49e122ed76c0bc3397a64af9574691cb8201b4e46d4ab2ecb95", "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://bcr.bazel.build/modules/protobuf/3.19.2/MODULE.bazel": "532ffe5f2186b69fdde039efe6df13ba726ff338c6bc82275ad433013fa10573", + "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858", "https://bcr.bazel.build/modules/protobuf/32.1/MODULE.bazel": "89cd2866a9cb07fee9ff74c41ceace11554f32e0d849de4e23ac55515cfada4d", "https://bcr.bazel.build/modules/protobuf/33.4/MODULE.bazel": "114775b816b38b6d0ca620450d6b02550c60ceedfdc8d9a229833b34a223dc42", "https://bcr.bazel.build/modules/protobuf/33.4/source.json": "555f8686b4c7d6b5ba731fbea13bf656b4bfd9a7ff629c1d9d3f6e1d6155de79", @@ -126,11 +143,17 @@ "https://bcr.bazel.build/modules/rules_cc/0.1.5/MODULE.bazel": "88dfc9361e8b5ae1008ac38f7cdfd45ad738e4fa676a3ad67d19204f045a1fd8", "https://bcr.bazel.build/modules/rules_cc/0.2.0/MODULE.bazel": "b5c17f90458caae90d2ccd114c81970062946f49f355610ed89bebf954f5783c", "https://bcr.bazel.build/modules/rules_cc/0.2.13/MODULE.bazel": "eecdd666eda6be16a8d9dc15e44b5c75133405e820f620a234acc4b1fdc5aa37", - "https://bcr.bazel.build/modules/rules_cc/0.2.14/MODULE.bazel": "353c99ed148887ee89c54a17d4100ae7e7e436593d104b668476019023b58df8", - "https://bcr.bazel.build/modules/rules_cc/0.2.14/source.json": "55d0a4587c5592fad350f6e698530f4faf0e7dd15e69d43f8d87e220c78bea54", + "https://bcr.bazel.build/modules/rules_cc/0.2.17/MODULE.bazel": "1849602c86cb60da8613d2de887f9566a6d354a6df6d7009f9d04a14402f9a84", + "https://bcr.bazel.build/modules/rules_cc/0.2.17/source.json": "3832f45d145354049137c0090df04629d9c2b5493dc5c2bf46f1834040133a07", "https://bcr.bazel.build/modules/rules_cc/0.2.8/MODULE.bazel": "f1df20f0bf22c28192a794f29b501ee2018fa37a3862a1a2132ae2940a23a642", "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", + "https://bcr.bazel.build/modules/rules_go/0.41.0/MODULE.bazel": "55861d8e8bb0e62cbd2896f60ff303f62ffcb0eddb74ecb0e5c0cbe36fc292c8", + "https://bcr.bazel.build/modules/rules_go/0.42.0/MODULE.bazel": "8cfa875b9aa8c6fce2b2e5925e73c1388173ea3c32a0db4d2b4804b453c14270", + "https://bcr.bazel.build/modules/rules_go/0.46.0/MODULE.bazel": "3477df8bdcc49e698b9d25f734c4f3a9f5931ff34ee48a2c662be168f5f2d3fd", + "https://bcr.bazel.build/modules/rules_go/0.59.0/MODULE.bazel": "b7e43e7414a3139a7547d1b4909b29085fbe5182b6c58cbe1ed4c6272815aeae", + "https://bcr.bazel.build/modules/rules_go/0.61.1/MODULE.bazel": "b599cc67f98e8dbe040631129fbd23f190a557f7be506d4781619d0fd4dbbbec", + "https://bcr.bazel.build/modules/rules_go/0.61.1/source.json": "ff52d46fca8e2ac87c610c11f05c3a9f0fa08dc4294664c6a31449092409c5aa", "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", "https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39", @@ -145,8 +168,8 @@ "https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017", "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939", "https://bcr.bazel.build/modules/rules_java/8.6.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2", - "https://bcr.bazel.build/modules/rules_java/9.0.3/MODULE.bazel": "1f98ed015f7e744a745e0df6e898a7c5e83562d6b759dfd475c76456dda5ccea", - "https://bcr.bazel.build/modules/rules_java/9.0.3/source.json": "b038c0c07e12e658135bbc32cc1a2ded6e33785105c9d41958014c592de4593e", + "https://bcr.bazel.build/modules/rules_java/9.1.0/MODULE.bazel": "ee63f27e36a3fada80342869361182f120a9819c74320e8e65b1e04ba0cd7a9d", + "https://bcr.bazel.build/modules/rules_java/9.1.0/source.json": "da589573c1dee2c9ac4a568b301269a2e8191110ff0345c1a959fa7ea6c4dfd6", "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", @@ -169,6 +192,7 @@ "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", "https://bcr.bazel.build/modules/rules_proto/6.0.0-rc1/MODULE.bazel": "1e5b502e2e1a9e825eef74476a5a1ee524a92297085015a052510b09a1a09483", "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", + "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2", "https://bcr.bazel.build/modules/rules_proto/7.1.0/MODULE.bazel": "002d62d9108f75bb807cd56245d45648f38275cb3a99dcd45dfb864c5d74cb96", "https://bcr.bazel.build/modules/rules_proto/7.1.0/source.json": "39f89066c12c24097854e8f57ab8558929f9c8d474d34b2c00ac04630ad8940e", "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", @@ -211,6 +235,7 @@ "https://bcr.bazel.build/modules/yq.bzl/0.1.1/MODULE.bazel": "9039681f9bcb8958ee2c87ffc74bdafba9f4369096a2b5634b88abc0eaefa072", "https://bcr.bazel.build/modules/yq.bzl/0.1.1/source.json": "2d2bad780a9f2b9195a4a370314d2c17ae95eaa745cefc2e12fbc49759b15aa3", "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", + "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806", "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" @@ -219,7 +244,7 @@ "moduleExtensions": { "//:repositories.bzl%extension": { "general": { - "bzlTransitiveDigest": "L4CX/d+aI/Fh+U2gUqaSZ+CCvlWyhNFteRiEumSSSGI=", + "bzlTransitiveDigest": "YTrMjY7a0alPdGQ0Yvth5IA7y1DqI88Q3FzntziUDfM=", "usagesDigest": "Cl/BMlQZ4+2G4wu2UOGGJvV/Tqz06ha25FDJ6s6QXb4=", "recordedInputs": [ "REPO_MAPPING:,aspect_bazel_lib aspect_bazel_lib+", @@ -319,7 +344,7 @@ }, "@@pybind11_bazel+//:internal_configure.bzl%internal_configure_extension": { "general": { - "bzlTransitiveDigest": "06cynZ1bCvvy8zHPrrDlXq+Z68xmjctHpfFxi+zEpJY=", + "bzlTransitiveDigest": "b+RP7Sgl8KN0VHamrgTqzGLuYPcQ/Mo4ptNkkHUIIlA=", "usagesDigest": "D1r3lfzMuUBFxgG8V6o0bQTLMk3GkaGOaPzw53wrwyw=", "recordedInputs": [ "REPO_MAPPING:pybind11_bazel+,bazel_tools bazel_tools", @@ -341,7 +366,7 @@ }, "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { "general": { - "bzlTransitiveDigest": "ABI1D/sbS1ovwaW/kHDoj8nnXjQ0oKU9fzmzEG4iT8o=", + "bzlTransitiveDigest": "Ga4z8lQy1YQ5rAMy+dOl0dqcCEBnYNCXku8x3YQmDZI=", "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", "recordedInputs": [ "REPO_MAPPING:rules_kotlin+,bazel_tools bazel_tools" @@ -398,7 +423,7 @@ }, "@@rules_python+//python/extensions:config.bzl%config": { "general": { - "bzlTransitiveDigest": "2hLgIvNVTLgxus0ZuXtleBe70intCfo0cHs8qvt6cdM=", + "bzlTransitiveDigest": "iibnRYgg8LpcfmH7EAnVwYePC3jsVaJ6Id8XxUjSZps=", "usagesDigest": "ZVSXMAGpD+xzVNPuvF1IoLBkty7TROO0+akMapt1pAg=", "recordedInputs": [ "REPO_MAPPING:rules_python+,bazel_tools bazel_tools", @@ -646,5 +671,332 @@ } } }, - "facts": {} + "facts": { + "@@rules_go+//go:extensions.bzl%go_sdk": { + "1.25.0": { + "aix_ppc64": [ + "go1.25.0.aix-ppc64.tar.gz", + "e5234a7dac67bc86c528fe9752fc9d63557918627707a733ab4cac1a6faed2d4" + ], + "darwin_amd64": [ + "go1.25.0.darwin-amd64.tar.gz", + "5bd60e823037062c2307c71e8111809865116714d6f6b410597cf5075dfd80ef" + ], + "darwin_arm64": [ + "go1.25.0.darwin-arm64.tar.gz", + "544932844156d8172f7a28f77f2ac9c15a23046698b6243f633b0a0b00c0749c" + ], + "dragonfly_amd64": [ + "go1.25.0.dragonfly-amd64.tar.gz", + "5ed3cf9a810a1483822538674f1336c06b51aa1b94d6d545a1a0319a48177120" + ], + "freebsd_386": [ + "go1.25.0.freebsd-386.tar.gz", + "abea5d5c6697e6b5c224731f2158fe87c602996a2a233ac0c4730cd57bf8374e" + ], + "freebsd_amd64": [ + "go1.25.0.freebsd-amd64.tar.gz", + "86e6fe0a29698d7601c4442052dac48bd58d532c51cccb8f1917df648138730b" + ], + "freebsd_arm": [ + "go1.25.0.freebsd-arm.tar.gz", + "d90b78e41921f72f30e8bbc81d9dec2cff7ff384a33d8d8debb24053e4336bfe" + ], + "freebsd_arm64": [ + "go1.25.0.freebsd-arm64.tar.gz", + "451d0da1affd886bfb291b7c63a6018527b269505db21ce6e14724f22ab0662e" + ], + "freebsd_riscv64": [ + "go1.25.0.freebsd-riscv64.tar.gz", + "7b565f76bd8bda46549eeaaefe0e53b251e644c230577290c0f66b1ecdb3cdbe" + ], + "illumos_amd64": [ + "go1.25.0.illumos-amd64.tar.gz", + "b1e1fdaab1ad25aa1c08d7a36c97d45d74b98b89c3f78c6d2145f77face54a2c" + ], + "linux_386": [ + "go1.25.0.linux-386.tar.gz", + "8c602dd9d99bc9453b3995d20ce4baf382cc50855900a0ece5de9929df4a993a" + ], + "linux_amd64": [ + "go1.25.0.linux-amd64.tar.gz", + "2852af0cb20a13139b3448992e69b868e50ed0f8a1e5940ee1de9e19a123b613" + ], + "linux_arm64": [ + "go1.25.0.linux-arm64.tar.gz", + "05de75d6994a2783699815ee553bd5a9327d8b79991de36e38b66862782f54ae" + ], + "linux_armv6l": [ + "go1.25.0.linux-armv6l.tar.gz", + "a5a8f8198fcf00e1e485b8ecef9ee020778bf32a408a4e8873371bfce458cd09" + ], + "linux_loong64": [ + "go1.25.0.linux-loong64.tar.gz", + "cab86b1cf761b1cb3bac86a8877cfc92e7b036fc0d3084123d77013d61432afc" + ], + "linux_mips": [ + "go1.25.0.linux-mips.tar.gz", + "d66b6fb74c3d91b9829dc95ec10ca1f047ef5e89332152f92e136cf0e2da5be1" + ], + "linux_mips64": [ + "go1.25.0.linux-mips64.tar.gz", + "4082e4381a8661bc2a839ff94ba3daf4f6cde20f8fb771b5b3d4762dc84198a2" + ], + "linux_mips64le": [ + "go1.25.0.linux-mips64le.tar.gz", + "70002c299ec7f7175ac2ef673b1b347eecfa54ae11f34416a6053c17f855afcc" + ], + "linux_mipsle": [ + "go1.25.0.linux-mipsle.tar.gz", + "b00a3a39eff099f6df9f1c7355bf28e4589d0586f42d7d4a394efb763d145a73" + ], + "linux_ppc64": [ + "go1.25.0.linux-ppc64.tar.gz", + "df166f33bd98160662560a72ff0b4ba731f969a80f088922bddcf566a88c1ec1" + ], + "linux_ppc64le": [ + "go1.25.0.linux-ppc64le.tar.gz", + "0f18a89e7576cf2c5fa0b487a1635d9bcbf843df5f110e9982c64df52a983ad0" + ], + "linux_riscv64": [ + "go1.25.0.linux-riscv64.tar.gz", + "c018ff74a2c48d55c8ca9b07c8e24163558ffec8bea08b326d6336905d956b67" + ], + "linux_s390x": [ + "go1.25.0.linux-s390x.tar.gz", + "34e5a2e19f2292fbaf8783e3a241e6e49689276aef6510a8060ea5ef54eee408" + ], + "netbsd_386": [ + "go1.25.0.netbsd-386.tar.gz", + "f8586cdb7aa855657609a5c5f6dbf523efa00c2bbd7c76d3936bec80aa6c0aba" + ], + "netbsd_amd64": [ + "go1.25.0.netbsd-amd64.tar.gz", + "ae8dc1469385b86a157a423bb56304ba45730de8a897615874f57dd096db2c2a" + ], + "netbsd_arm": [ + "go1.25.0.netbsd-arm.tar.gz", + "1ff7e4cc764425fc9dd6825eaee79d02b3c7cafffbb3691687c8d672ade76cb7" + ], + "netbsd_arm64": [ + "go1.25.0.netbsd-arm64.tar.gz", + "e1b310739f26724216aa6d7d7208c4031f9ff54c9b5b9a796ddc8bebcb4a5f16" + ], + "openbsd_386": [ + "go1.25.0.openbsd-386.tar.gz", + "4802a9b20e533da91adb84aab42e94aa56cfe3e5475d0550bed3385b182e69d8" + ], + "openbsd_amd64": [ + "go1.25.0.openbsd-amd64.tar.gz", + "c016cd984bebe317b19a4f297c4f50def120dc9788490540c89f28e42f1dabe1" + ], + "openbsd_arm": [ + "go1.25.0.openbsd-arm.tar.gz", + "a1e31d0bf22172ddde42edf5ec811ef81be43433df0948ece52fecb247ccfd8d" + ], + "openbsd_arm64": [ + "go1.25.0.openbsd-arm64.tar.gz", + "343ea8edd8c218196e15a859c6072d0dd3246fbbb168481ab665eb4c4140458d" + ], + "openbsd_ppc64": [ + "go1.25.0.openbsd-ppc64.tar.gz", + "694c14da1bcaeb5e3332d49bdc2b6d155067648f8fe1540c5de8f3cf8e157154" + ], + "openbsd_riscv64": [ + "go1.25.0.openbsd-riscv64.tar.gz", + "aa510ad25cf54c06cd9c70b6d80ded69cb20188ac6e1735655eef29ff7e7885f" + ], + "plan9_386": [ + "go1.25.0.plan9-386.tar.gz", + "46f8cef02086cf04bf186c5912776b56535178d4cb319cd19c9fdbdd29231986" + ], + "plan9_amd64": [ + "go1.25.0.plan9-amd64.tar.gz", + "29b34391d84095e44608a228f63f2f88113a37b74a79781353ec043dfbcb427b" + ], + "plan9_arm": [ + "go1.25.0.plan9-arm.tar.gz", + "0a047107d13ebe7943aaa6d54b1d7bbd2e45e68ce449b52915a818da715799c2" + ], + "solaris_amd64": [ + "go1.25.0.solaris-amd64.tar.gz", + "9977f9e4351984364a3b2b78f8b88bfd1d339812356d5237678514594b7d3611" + ], + "windows_386": [ + "go1.25.0.windows-386.zip", + "df9f39db82a803af0db639e3613a36681ab7a42866b1384b3f3a1045663961a7" + ], + "windows_amd64": [ + "go1.25.0.windows-amd64.zip", + "89efb4f9b30812eee083cc1770fdd2913c14d301064f6454851428f9707d190b" + ], + "windows_arm64": [ + "go1.25.0.windows-arm64.zip", + "27bab004c72b3d7bd05a69b6ec0fc54a309b4b78cc569dd963d8b3ec28bfdb8c" + ] + }, + "1.25.5": { + "aix_ppc64": [ + "go1.25.5.aix-ppc64.tar.gz", + "89d3300aeb8a49354c04d43abf2a0dca6a75ac772b3bc2edaac52de513041572" + ], + "darwin_amd64": [ + "go1.25.5.darwin-amd64.tar.gz", + "b69d51bce599e5381a94ce15263ae644ec84667a5ce23d58dc2e63e2c12a9f56" + ], + "darwin_arm64": [ + "go1.25.5.darwin-arm64.tar.gz", + "bed8ebe824e3d3b27e8471d1307f803fc6ab8e1d0eb7a4ae196979bd9b801dd3" + ], + "dragonfly_amd64": [ + "go1.25.5.dragonfly-amd64.tar.gz", + "51478a265f45b68ce761aa303c1ecf185949f6eb0b2106e3066ec4d32361b38a" + ], + "freebsd_386": [ + "go1.25.5.freebsd-386.tar.gz", + "f8ff9fa5309fbbbd7d52f5d3f7181feb830dfd044d23c38746a2ada091f751b5" + ], + "freebsd_amd64": [ + "go1.25.5.freebsd-amd64.tar.gz", + "a2d2b2aeb218bd646fd8708bacc96c9d4de1b6c9ea48ceb9171e9e784f676650" + ], + "freebsd_arm": [ + "go1.25.5.freebsd-arm.tar.gz", + "b83a5cb1695c7185a13840661aef6aa1b46202d41a72528ecde51735765c6641" + ], + "freebsd_arm64": [ + "go1.25.5.freebsd-arm64.tar.gz", + "938fc0204f853c24ab03967105146af6590903dd14f869fe912db7a735f654f6" + ], + "freebsd_riscv64": [ + "go1.25.5.freebsd-riscv64.tar.gz", + "7b0cc61246cf6fc9e576135cfcd2b95e870b0f2ee5bf057325b2d76119001e4e" + ], + "illumos_amd64": [ + "go1.25.5.illumos-amd64.tar.gz", + "ea5cafcf995ef4a82ced5a7134f18bbc5ca554c345075d18f37d2e192fe2c1ff" + ], + "linux_386": [ + "go1.25.5.linux-386.tar.gz", + "db908a86e888574ed3432355ba5372ad3ef2c0821ba9b91ceaa0f6634620c40c" + ], + "linux_amd64": [ + "go1.25.5.linux-amd64.tar.gz", + "9e9b755d63b36acf30c12a9a3fc379243714c1c6d3dd72861da637f336ebb35b" + ], + "linux_arm64": [ + "go1.25.5.linux-arm64.tar.gz", + "b00b694903d126c588c378e72d3545549935d3982635ba3f7a964c9fa23fe3b9" + ], + "linux_armv6l": [ + "go1.25.5.linux-armv6l.tar.gz", + "0b27e3dec8d04899d6941586d2aa2721c3dee67c739c1fc1b528188f3f6e8ab5" + ], + "linux_loong64": [ + "go1.25.5.linux-loong64.tar.gz", + "0be2f27172a85de1b9c0c7f324832023b177d07cfa04a55dc67a3cc965fe969e" + ], + "linux_mips": [ + "go1.25.5.linux-mips.tar.gz", + "0e7c387a6914c81b53278f023da2004e17d8d8e851749cdb5df15ad59e54ff3e" + ], + "linux_mips64": [ + "go1.25.5.linux-mips64.tar.gz", + "32ef8f4c4896c1e88dcbbf79c353d3e46dc38410e649327ee47e073e3326b06f" + ], + "linux_mips64le": [ + "go1.25.5.linux-mips64le.tar.gz", + "437dc493b3ce97a65e7abc7e8ecb935f504b4805aff749baba219841bd970335" + ], + "linux_mipsle": [ + "go1.25.5.linux-mipsle.tar.gz", + "675cd3e0cb7d9131602a0de06f5471ce969fd95d388baf1ec05106a1cdeb20b6" + ], + "linux_ppc64": [ + "go1.25.5.linux-ppc64.tar.gz", + "a2159b254b025a816673365db565b3bd64f99fb185eb3f4cedabbf992097304d" + ], + "linux_ppc64le": [ + "go1.25.5.linux-ppc64le.tar.gz", + "f0904b647b5b8561efc5d48bb59a34f2b7996afab83ccd41c93b1aeb2c0067e4" + ], + "linux_riscv64": [ + "go1.25.5.linux-riscv64.tar.gz", + "05de84b319bc91b9cecbc6bf8eb5fcd814cf8a9d16c248d293dbd96f6cc0151b" + ], + "linux_s390x": [ + "go1.25.5.linux-s390x.tar.gz", + "a5d0a72b0dfd57f9c2c0cdd8b7e0f401e0afb9e8c304d3410f9b0982ce0953da" + ], + "netbsd_386": [ + "go1.25.5.netbsd-386.tar.gz", + "57e79e72d1110954c6567082137f0228c46eb4d612211b3bf48dadb6a27eeaa2" + ], + "netbsd_amd64": [ + "go1.25.5.netbsd-amd64.tar.gz", + "d6062fa06c33613be60436b3e6422f2de43c28350bb30dbbc459782985eea7bd" + ], + "netbsd_arm": [ + "go1.25.5.netbsd-arm.tar.gz", + "457d844df4aa6cd51616b2334e378cc295ee7bcca1cb21869adfc16f5f379bfd" + ], + "netbsd_arm64": [ + "go1.25.5.netbsd-arm64.tar.gz", + "5030511891f670dba65a6d8f15e687f623030ac5b63661a423a7dad53990b634" + ], + "openbsd_386": [ + "go1.25.5.openbsd-386.tar.gz", + "27cba5feeedfb08dea2770420c6024d7ea72eedad08132a9759c0f05f66f2486" + ], + "openbsd_amd64": [ + "go1.25.5.openbsd-amd64.tar.gz", + "c873e93f6bd125a23b359914ba34d62e6af21111b06c14ce344b724aa1ef933b" + ], + "openbsd_arm": [ + "go1.25.5.openbsd-arm.tar.gz", + "e5a8cc469c66248bc2031327e66a4ad48a8f379435677386db52ef1aa7b2b0e2" + ], + "openbsd_arm64": [ + "go1.25.5.openbsd-arm64.tar.gz", + "cdd655fa0e15bc839d0c353bfb728b8d22a8012e54a11e297be919c3cf7ad866" + ], + "openbsd_ppc64": [ + "go1.25.5.openbsd-ppc64.tar.gz", + "a149370fda34ce27fa78a452248f6f2040fe5ab955dce246d4d92de2c2d511c1" + ], + "openbsd_riscv64": [ + "go1.25.5.openbsd-riscv64.tar.gz", + "be96cf8460011088cb75b330357796ecf1ac2ffffac77a3d1f798cbd7e6a2bc0" + ], + "plan9_386": [ + "go1.25.5.plan9-386.tar.gz", + "a3926108324c4b161080b03cd941db58090fba1194610093ee5716ee98997287" + ], + "plan9_amd64": [ + "go1.25.5.plan9-amd64.tar.gz", + "f171e529236b850ed4a1e714c317dddcf46855b575d9d018c654cf78b3ea1a2e" + ], + "plan9_arm": [ + "go1.25.5.plan9-arm.tar.gz", + "aaf285e49f39717029f4759c0f9b2860dbf23c20fb659122006ba1e145643721" + ], + "solaris_amd64": [ + "go1.25.5.solaris-amd64.tar.gz", + "fb61c56ca80a2e1e5f74609ded64546166a5ab431c0d581ce718310cf27f6b9f" + ], + "windows_386": [ + "go1.25.5.windows-386.zip", + "a593393ea7715ffd315158f622a76226c3a4c4a0a6f92b1aeae03d7380cc06a3" + ], + "windows_amd64": [ + "go1.25.5.windows-amd64.zip", + "ae756cce1cb80c819b4fe01b0353807178f532211b47f72d7fa77949de054ebb" + ], + "windows_arm64": [ + "go1.25.5.windows-arm64.zip", + "55a94a423a6b8f3ac2ac4d05a6e44d7760c6520a2c6dcef7425f6bac79c4eece" + ] + } + } + } } diff --git a/README.md b/README.md index 5b7f6d51..82dc1bda 100644 --- a/README.md +++ b/README.md @@ -299,6 +299,7 @@ The following list of options are currently supported: containerRunOptions: user: "root" # set the --user/-u flag privileged: true # set the --privileged flag (default: false) + network: "bridge" # set the --net flag (default: bridge) allocateTty: true # set the --tty flag (default: false) envFile: path/to/.env # load environment variables from file and pass to container (equivalent to --env-file) envVars: # if not empty, read each envVar from the environment and pass to test (equivalent to --env/e) @@ -306,6 +307,11 @@ containerRunOptions: - OTHER_SECRET_BAR capabilities: # Add list of Linux capabilities (--cap-add) - NET_BIND_SERVICE + drop_capabilities: # Drop list of Linux capabilities (--cap-drop) + - NET_BIND_SERVICE + sysctls: # set namespaced kernel parameters (--sysctl) + net.core.somaxconn: "1024" + net.ipv4.tcp_max_syn_backlog: "4096" bindMounts: # Bind mount a volume (--volume, -v) - /etc/example/dir:/etc/dir ``` diff --git a/bazel/BUILD.bazel b/bazel/BUILD.bazel index 66528b57..4dbd9578 100644 --- a/bazel/BUILD.bazel +++ b/bazel/BUILD.bazel @@ -1,4 +1,5 @@ load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +load(":toolchain.bzl", "structure_test_toolchain") package(default_visibility = ["//visibility:public"]) @@ -6,6 +7,23 @@ toolchain_type( name = "structure_test_toolchain_type", ) +# Smoke-test-only toolchain backed by the from-source go_binary instead of a +# downloaded release. The bazel/test smoke module registers +# //bazel:from_source_structure_test_toolchain ahead of the downloaded +# toolchains so HEAD's rule definition is exercised against HEAD's binary. +# Real consumers never reach this target; they get the release toolchain wired +# up by repositories.bzl. +structure_test_toolchain( + name = "from_source_structure_test", + structure_test = "//cmd/container-structure-test", +) + +toolchain( + name = "from_source_structure_test_toolchain", + toolchain = ":from_source_structure_test", + toolchain_type = ":structure_test_toolchain_type", +) + bzl_library( name = "container_structure_test", srcs = ["container_structure_test.bzl"], diff --git a/bazel/container_structure_test.bzl b/bazel/container_structure_test.bzl index cd5d0088..6b2a7855 100644 --- a/bazel/container_structure_test.bzl +++ b/bazel/container_structure_test.bzl @@ -16,8 +16,7 @@ _attrs = { doc = "See https://github.com/GoogleContainerTools/container-structure-test#running-file-tests-without-docker", ), "platform": attr.string( - default = "linux/amd64", - doc = "Set platform if host is multi-platform capable (default \"linux/amd64\")", + doc = "Set platform if host is multi-platform capable", ), "_runfiles": attr.label(default = "@bazel_tools//tools/bash/runfiles"), "_windows_constraint": attr.label(default = "@platforms//os:windows"), @@ -37,21 +36,44 @@ readonly image=$(rlocation {image_path}) # When the image points to a folder, we can read the index.json file inside if [[ -d "$image" ]]; then readonly DIGEST=$("$jq" -r '.manifests[0].digest | sub(":"; "-")' "$image/index.json") - exec "$st" test --driver {driver} {fixed_args} --default-image-tag "cst.oci.local/$DIGEST:$DIGEST" $@ + readonly TEST_TAG="cst.oci.local/$DIGEST:$DIGEST" + + # Register a cleanup trap to remove the loaded image from Docker + cleanup() {{ + docker rmi "$TEST_TAG" >/dev/null 2>&1 || true + }} + trap cleanup EXIT + + "$st" test --driver {driver} {fixed_args} --default-image-tag "$TEST_TAG" "$@" + exit "$?" else - exec "$st" test --driver {driver} {fixed_args} $@ + exec "$st" test --driver {driver} {fixed_args} "$@" fi """ def _structure_test_impl(ctx): + # --test-report writes to the file specified by $XML_OUTPUT_FILE + # --test-report-format junit ensures bazel-compatible JUnit XML output + # stdout uses default text format for human-readable output (captured as test.log) fixed_args = [ "--test-report $XML_OUTPUT_FILE", - "--output junit", - "--junit-suite-name $TEST_TARGET" + "--test-report-format junit", + "--junit-suite-name $TEST_TARGET", ] test_bin = ctx.toolchains["@container_structure_test//bazel:structure_test_toolchain_type"].st_info.binary jq_bin = ctx.toolchains["@aspect_bazel_lib//lib:jq_toolchain_type"].jqinfo.bin + # On Windows the downloaded binary has no file extension. Windows requires + # a .exe extension to recognise a file as directly executable — without it, + # invoking the binary from a batch script triggers an "Open With" dialog + # instead of running it. Symlink the binary to .exe so the generated + # launcher scripts can call it without this problem. + is_windows = ctx.target_platform_has_constraint(ctx.attr._windows_constraint[platform_common.ConstraintValueInfo]) + if is_windows and not test_bin.basename.endswith(".exe"): + test_bin_exe = ctx.actions.declare_file(test_bin.basename + ".exe") + ctx.actions.symlink(output = test_bin_exe, target_file = test_bin) + test_bin = test_bin_exe + default_info = ctx.attr.image[DefaultInfo] if DefaultInfo in ctx.attr.image else None output_group_info = ctx.attr.image[OutputGroupInfo] if OutputGroupInfo in ctx.attr.image else None image = None @@ -100,7 +122,6 @@ def _structure_test_impl(ctx): is_executable = True, ) - is_windows = ctx.target_platform_has_constraint(ctx.attr._windows_constraint[platform_common.ConstraintValueInfo]) launcher = create_windows_native_launcher_script(ctx, bash_launcher) if is_windows else bash_launcher runfiles = ctx.runfiles( files = image_files + ctx.files.configs + [ diff --git a/bazel/docs/defs.md b/bazel/docs/defs.md index 2dd1f45d..c077579e 100644 --- a/bazel/docs/defs.md +++ b/bazel/docs/defs.md @@ -39,6 +39,6 @@ https://github.com/GoogleContainerTools/container-structure-test#running-file-te | configs | - | List of labels | required | | | driver | See https://github.com/GoogleContainerTools/container-structure-test#running-file-tests-without-docker | String | optional | `"docker"` | | image | Label of an oci_image or oci_tarball target. | Label | optional | `None` | -| platform | Set platform if host is multi-platform capable (default "linux/amd64") | String | optional | `"linux/amd64"` | +| platform | Set platform if host is multi-platform capable | String | optional | `""` | diff --git a/bazel/test/.bazelrc b/bazel/test/.bazelrc index 6ae2a3e4..060287b3 100644 --- a/bazel/test/.bazelrc +++ b/bazel/test/.bazelrc @@ -1 +1,6 @@ # Bazel options go here + +# Required so the container_structure_test module's isolated go_deps usage (which +# carries the docker proto-disable gazelle_override) is accepted while building +# the from-source binary that backs the smoke-test toolchain. +common --experimental_isolated_extension_usages diff --git a/bazel/test/.bazelversion b/bazel/test/.bazelversion index f3b5af39..815da58b 100644 --- a/bazel/test/.bazelversion +++ b/bazel/test/.bazelversion @@ -1 +1 @@ -6.1.1 +7.4.1 diff --git a/bazel/test/MODULE.bazel b/bazel/test/MODULE.bazel index 0af2c225..8bacfe48 100644 --- a/bazel/test/MODULE.bazel +++ b/bazel/test/MODULE.bazel @@ -7,7 +7,20 @@ module( bazel_dep(name = "container_structure_test", version = "0.0.0") +# The from-source toolchain (registered below) compiles +# @container_structure_test//cmd/container-structure-test, a go_binary. rules_go +# must be visible under its io_bazel_rules_go apparent name so Go build settings +# such as --@io_bazel_rules_go//go/config:pure resolve in this root module. +bazel_dep(name = "rules_go", version = "0.61.1", repo_name = "io_bazel_rules_go") + local_path_override( module_name = "container_structure_test", path = "../..", ) + +# Register the from-source toolchain first so toolchain resolution prefers +# HEAD's go_binary over the downloaded release toolchains that +# container_structure_test registers. Earliest registration wins, and the root +# module's registrations precede those of its dependencies. This makes the +# smoke test exercise HEAD's rule against HEAD's binary. +register_toolchains("@container_structure_test//bazel:from_source_structure_test_toolchain") diff --git a/bazel/test/README.md b/bazel/test/README.md index 5a351f52..3cc68cd7 100644 --- a/bazel/test/README.md +++ b/bazel/test/README.md @@ -1,38 +1,33 @@ # Bazel smoke test -Verifies that the container_structure_test bazel rule exposed by this project works properly. +Verifies that the `container_structure_test` bazel rule exposed by this project +works properly, exercising HEAD's rule against a from-source build of HEAD's +binary (`//cmd/container-structure-test`) rather than a downloaded release. -## Running tests with pre-compiled toolchain +## Running ```sh cd bazel/test -bazel test ... -bazel test --enable_bzlmod ... +bazel test //... # bzlmod (default) +bazel test //... --enable_bzlmod=false --enable_workspace # WORKSPACE mode ``` -## Testing with local changes (non-pre-compiled toolchain) - -When developing changes, you may want to test your modifications before they're compiled a release. Here's how to test with a locally built binary: - -1. Build your local binary: - ```sh - go build -o /tmp/container-structure-test-local ./cmd/container-structure-test/ - ``` - -2. Temporarily modify `bazel/container_structure_test.bzl`: - ```sh - sed -i.bak 's|readonly st=$(rlocation {st_path})|readonly st="/tmp/container-structure-test-local"|g' bazel/container_structure_test.bzl - ``` - -3. Run the bazel test: - ```sh - cd bazel/test - bazel test :test --test_output=all - ``` - -4. Restore the original rule: - ```sh - mv bazel/container_structure_test.bzl.bak bazel/container_structure_test.bzl - ``` - -This allows you to verify that your changes work correctly with the bazel integration before submitting them. +Both module systems register the from-source `structure_test_toolchain` +(`@container_structure_test//bazel:from_source_structure_test_toolchain`) ahead +of the downloaded release toolchains, so resolution prefers HEAD's binary. This +means changes to either the rule (`bazel/container_structure_test.bzl`) or the +Go binary are tested together, with no manual binary-swapping. + +## How the from-source toolchain is wired + +- **bzlmod**: `MODULE.bazel` brings `rules_go`/`gazelle` and registers the + from-source toolchain. Go module deps come from the isolated `go_deps` + extension reading the root `//:go.mod`. +- **WORKSPACE**: `WORKSPACE.bazel` reconstructs the same wiring (the repo itself + is bzlmod-only, with an empty root `WORKSPACE.bazel`). Go module deps live in + the generated `go_deps.bzl` macro; regenerate it after `go.mod` changes per + the header comment in that file. + +Real consumers do not use the from-source toolchain — they get the downloaded +release toolchain via `container_structure_test_register_toolchain` +(`repositories.bzl`) and need no `rules_go`. diff --git a/bazel/test/WORKSPACE.bazel b/bazel/test/WORKSPACE.bazel index 5ee1735e..86bcfd33 100644 --- a/bazel/test/WORKSPACE.bazel +++ b/bazel/test/WORKSPACE.bazel @@ -16,12 +16,74 @@ http_archive( ], ) +# rules_go + gazelle back the from-source toolchain registered below, which +# compiles @container_structure_test//cmd/container-structure-test (a go_binary) +# so the smoke test exercises HEAD's rule against HEAD's binary. The repo itself +# is bzlmod-only (its root WORKSPACE.bazel is empty), so this WORKSPACE client +# reconstructs the rules_go/gazelle/go_deps wiring that the root MODULE.bazel +# provides under bzlmod. The repo name MUST be io_bazel_rules_go: generated Go +# BUILD files and rules_go internals hard-code that apparent name. +# +# Pinned to rules_go 0.60.0 here (bzlmod uses 0.61.1). The version is bounded on +# both sides: rules_go 0.55.1 fails to build the go 1.25.5 stdlib because it +# still sets the `coverageredesign` GOEXPERIMENT that go 1.25 removed (fixed in +# rules_go 0.56.0); and rules_go 0.61.1+ use bool_setting(scope = ...) in +# go/private, whose `scope` attribute is only accepted in WORKSPACE mode on Bazel +# >= 8.3, breaking the 7.4.1 and 8.0.0 legs. 0.60.0 is the newest release in that +# window: it supports go 1.25.5 and still analyzes in WORKSPACE mode across the CI +# bazel matrix. The skew from bzlmod's 0.61.1 only affects how the smoke binary is +# built; both produce a binary supporting --test-report-format, which is what the +# rule under test needs. +http_archive( + name = "io_bazel_rules_go", + integrity = "sha256-htPcj1nSU1JPkzqvLzwFiWywtgX8NbRgwLSwOZlhJMY=", + urls = [ + "https://mirror.bazel.build/github.com/bazel-contrib/rules_go/releases/download/v0.60.0/rules_go-v0.60.0.zip", + "https://github.com/bazel-contrib/rules_go/releases/download/v0.60.0/rules_go-v0.60.0.zip", + ], +) + +http_archive( + name = "bazel_gazelle", + integrity = "sha256-SdnrowmwtpWCT/QX1zQkKCStmrXttWBjudNADfGmGlY=", + urls = [ + "https://mirror.bazel.build/github.com/bazel-contrib/bazel-gazelle/releases/download/v0.51.3/bazel-gazelle-v0.51.3.tar.gz", + "https://github.com/bazel-contrib/bazel-gazelle/releases/download/v0.51.3/bazel-gazelle-v0.51.3.tar.gz", + ], +) + +load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies") +load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies") + +# go_deps.bzl is generated from //:go.mod via +# `bazel run //:gazelle -- update-repos -from_file=go.mod -to_macro=bazel/test/go_deps.bzl%go_dependencies -prune` +# (regenerate when go.mod changes). It must run before go_rules_dependencies so +# its go_repository declarations win over rules_go's defaults. +load("//:go_deps.bzl", "go_dependencies") + +# gazelle:repository_macro go_deps.bzl%go_dependencies +go_dependencies() + +go_rules_dependencies() + +go_register_toolchains(version = "1.25.5") + +# Point gazelle's repo-config scan at this file; the default "@//:WORKSPACE" +# does not exist because this module uses the WORKSPACE.bazel name. +gazelle_dependencies(go_repository_default_config = "@//:WORKSPACE.bazel") + local_repository( name = "container_structure_test", path = "../..", ) +# Register the from-source toolchain first so toolchain resolution prefers +# HEAD's go_binary over the downloaded release toolchains registered below. +# In WORKSPACE mode the earliest matching registration wins, mirroring the +# bzlmod ordering set up in MODULE.bazel. This makes the smoke test exercise +# HEAD's rule against HEAD's binary in both module systems. +register_toolchains("@container_structure_test//bazel:from_source_structure_test_toolchain") + load("@container_structure_test//:repositories.bzl", "container_structure_test_register_toolchain") container_structure_test_register_toolchain(name = "cst") - diff --git a/bazel/test/go_deps.bzl b/bazel/test/go_deps.bzl new file mode 100644 index 00000000..23a5858d --- /dev/null +++ b/bazel/test/go_deps.bzl @@ -0,0 +1,1180 @@ +# Go module dependencies for the WORKSPACE-mode from-source toolchain build. +# These mirror the bzlmod go_deps extension (root MODULE.bazel) for legacy +# WORKSPACE consumers; bzlmod builds do not load this file. +# +# Regenerate after go.mod changes (run from the repo root, then move the macro +# registration into bazel/test/WORKSPACE.bazel rather than the root WORKSPACE): +# bazel run //:gazelle -- update-repos -from_file=go.mod \ +# -to_macro=bazel/test/go_deps.bzl%go_dependencies -prune +# +# After regenerating, reapply two manual edits gazelle does not emit: +# 1. the load() below must reference @bazel_gazelle (WORKSPACE repo name), +# not @gazelle (the bzlmod apparent name). +# 2. the com_github_docker_docker entry needs +# build_directives = ["gazelle:proto disable_global"] (see below). +load("@bazel_gazelle//:deps.bzl", "go_repository") + +def go_dependencies(): + go_repository( + name = "cat_dario_mergo", + importpath = "dario.cat/mergo", + sum = "h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=", + version = "v1.0.0", + ) + go_repository( + name = "com_github_adalogics_go_fuzz_headers", + importpath = "github.com/AdaLogics/go-fuzz-headers", + sum = "h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=", + version = "v0.0.0-20240806141605-e8a1dd7889d6", + ) + go_repository( + name = "com_github_adamkorcz_go_118_fuzz_build", + importpath = "github.com/AdamKorcz/go-118-fuzz-build", + sum = "h1:59MxjQVfjXsBpLy+dbd2/ELV5ofnUkUZBvWSC85sheA=", + version = "v0.0.0-20230306123547-8075edf89bb0", + ) + go_repository( + name = "com_github_azure_go_ansiterm", + importpath = "github.com/Azure/go-ansiterm", + sum = "h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=", + version = "v0.0.0-20250102033503-faa5f7b0171c", + ) + go_repository( + name = "com_github_beorn7_perks", + importpath = "github.com/beorn7/perks", + sum = "h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=", + version = "v1.0.1", + ) + go_repository( + name = "com_github_blang_semver_v4", + importpath = "github.com/blang/semver/v4", + sum = "h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=", + version = "v4.0.0", + ) + go_repository( + name = "com_github_cenkalti_backoff_v4", + importpath = "github.com/cenkalti/backoff/v4", + sum = "h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=", + version = "v4.2.1", + ) + go_repository( + name = "com_github_cespare_xxhash_v2", + importpath = "github.com/cespare/xxhash/v2", + sum = "h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=", + version = "v2.3.0", + ) + go_repository( + name = "com_github_cilium_ebpf", + importpath = "github.com/cilium/ebpf", + sum = "h1:64sn2K3UKw8NbP/blsixRpF3nXuyhz/VjRlRzvlBRu4=", + version = "v0.9.1", + ) + go_repository( + name = "com_github_containerd_aufs", + importpath = "github.com/containerd/aufs", + sum = "h1:2oeJiwX5HstO7shSrPZjrohJZLzK36wvpdmzDRkL/LY=", + version = "v1.0.0", + ) + go_repository( + name = "com_github_containerd_btrfs_v2", + importpath = "github.com/containerd/btrfs/v2", + sum = "h1:FN4wsx7KQrYoLXN7uLP0vBV4oVWHOIKDRQ1G2Z0oL5M=", + version = "v2.0.0", + ) + go_repository( + name = "com_github_containerd_cgroups", + importpath = "github.com/containerd/cgroups", + sum = "h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=", + version = "v1.1.0", + ) + go_repository( + name = "com_github_containerd_cgroups_v3", + importpath = "github.com/containerd/cgroups/v3", + sum = "h1:f5WFqIVSgo5IZmtTT3qVBo6TzI1ON6sycSBKkymb9L0=", + version = "v3.0.2", + ) + go_repository( + name = "com_github_containerd_console", + importpath = "github.com/containerd/console", + sum = "h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=", + version = "v1.0.3", + ) + go_repository( + name = "com_github_containerd_containerd", + importpath = "github.com/containerd/containerd", + sum = "h1:iAkYGC/ifR/V+0eR4iXWHNGYUF0DF2PmGV5iz4Irj5M=", + version = "v1.7.33", + ) + go_repository( + name = "com_github_containerd_containerd_api", + importpath = "github.com/containerd/containerd/api", + sum = "h1:hVTNJKR8fMc/2Tiw60ZRijntNMd1U+JVMyTRdsD2bS0=", + version = "v1.8.0", + ) + go_repository( + name = "com_github_containerd_continuity", + importpath = "github.com/containerd/continuity", + sum = "h1:/fNVfTJ7wIl/YPMHjf+5H32uFhl63JucB34PlCpMKII=", + version = "v0.4.4", + ) + go_repository( + name = "com_github_containerd_errdefs", + importpath = "github.com/containerd/errdefs", + sum = "h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=", + version = "v1.0.0", + ) + go_repository( + name = "com_github_containerd_errdefs_pkg", + importpath = "github.com/containerd/errdefs/pkg", + sum = "h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=", + version = "v0.3.0", + ) + + go_repository( + name = "com_github_containerd_fifo", + importpath = "github.com/containerd/fifo", + sum = "h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY=", + version = "v1.1.0", + ) + go_repository( + name = "com_github_containerd_go_cni", + importpath = "github.com/containerd/go-cni", + sum = "h1:ORi7P1dYzCwVM6XPN4n3CbkuOx/NZ2DOqy+SHRdo9rU=", + version = "v1.1.9", + ) + go_repository( + name = "com_github_containerd_go_runc", + importpath = "github.com/containerd/go-runc", + sum = "h1:oU+lLv1ULm5taqgV/CJivypVODI4SUz1znWjv3nNYS0=", + version = "v1.0.0", + ) + go_repository( + name = "com_github_containerd_imgcrypt", + importpath = "github.com/containerd/imgcrypt", + sum = "h1:ZS7TuywcRNLoHpU0g+v4/PsKynl6TYlw5xDVWWoIyFA=", + version = "v1.1.8", + ) + go_repository( + name = "com_github_containerd_log", + importpath = "github.com/containerd/log", + sum = "h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=", + version = "v0.1.0", + ) + go_repository( + name = "com_github_containerd_nri", + importpath = "github.com/containerd/nri", + sum = "h1:n1S753B9lX8RFrHYeSgwVvS1yaUcHjxbB+f+xzEncRI=", + version = "v0.8.0", + ) + go_repository( + name = "com_github_containerd_platforms", + importpath = "github.com/containerd/platforms", + sum = "h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=", + version = "v0.2.1", + ) + go_repository( + name = "com_github_containerd_stargz_snapshotter_estargz", + importpath = "github.com/containerd/stargz-snapshotter/estargz", + sum = "h1:cy2/lpgBXDA3cDKSyEfNOFMA/c10O1axL69EU7iirO8=", + version = "v0.18.1", + ) + go_repository( + name = "com_github_containerd_ttrpc", + importpath = "github.com/containerd/ttrpc", + sum = "h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ=", + version = "v1.2.7", + ) + go_repository( + name = "com_github_containerd_typeurl", + importpath = "github.com/containerd/typeurl", + sum = "h1:Chlt8zIieDbzQFzXzAeBEF92KhExuE4p9p92/QmY7aY=", + version = "v1.0.2", + ) + go_repository( + name = "com_github_containerd_typeurl_v2", + importpath = "github.com/containerd/typeurl/v2", + sum = "h1:6NBDbQzr7I5LHgp34xAXYF5DOTQDn05X58lsPEmzLso=", + version = "v2.2.0", + ) + go_repository( + name = "com_github_containerd_zfs", + importpath = "github.com/containerd/zfs", + sum = "h1:n7OZ7jZumLIqNJqXrEc/paBM840mORnmGdJDmAmJZHM=", + version = "v1.1.0", + ) + go_repository( + name = "com_github_containernetworking_cni", + importpath = "github.com/containernetworking/cni", + sum = "h1:wtRGZVv7olUHMOqouPpn3cXJWpJgM6+EUl31EQbXALQ=", + version = "v1.1.2", + ) + go_repository( + name = "com_github_containernetworking_plugins", + importpath = "github.com/containernetworking/plugins", + sum = "h1:SWgg3dQG1yzUo4d9iD8cwSVh1VqI+bP7mkPDoSfP9VU=", + version = "v1.2.0", + ) + go_repository( + name = "com_github_containers_ocicrypt", + importpath = "github.com/containers/ocicrypt", + sum = "h1:r7UR6o8+lyhkEywetubUUgcKFjOWOaWz8cEBrCPX0ic=", + version = "v1.1.10", + ) + go_repository( + name = "com_github_coreos_go_systemd_v22", + importpath = "github.com/coreos/go-systemd/v22", + sum = "h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=", + version = "v22.5.0", + ) + go_repository( + name = "com_github_cpuguy83_go_md2man_v2", + importpath = "github.com/cpuguy83/go-md2man/v2", + sum = "h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=", + version = "v2.0.7", + ) + go_repository( + name = "com_github_creack_pty", + importpath = "github.com/creack/pty", + sum = "h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=", + version = "v1.1.24", + ) + go_repository( + name = "com_github_cyphar_filepath_securejoin", + importpath = "github.com/cyphar/filepath-securejoin", + sum = "h1:eYgfMq5yryL4fbWfkLpFFy2ukSELzaJOTaUTuh+oF48=", + version = "v0.5.1", + ) + go_repository( + name = "com_github_danieljoos_wincred", + importpath = "github.com/danieljoos/wincred", + sum = "h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0=", + version = "v1.2.2", + ) + go_repository( + name = "com_github_davecgh_go_spew", + importpath = "github.com/davecgh/go-spew", + sum = "h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=", + version = "v1.1.1", + ) + go_repository( + name = "com_github_distribution_reference", + importpath = "github.com/distribution/reference", + sum = "h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=", + version = "v0.6.0", + ) + go_repository( + name = "com_github_docker_cli", + importpath = "github.com/docker/cli", + sum = "h1:nbEFfz774vBwQ5KRYv7c/AghjReqnGISvrRhzjV0evs=", + version = "v29.5.3+incompatible", + ) + go_repository( + name = "com_github_docker_distribution", + importpath = "github.com/docker/distribution", + sum = "h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=", + version = "v2.8.3+incompatible", + ) + go_repository( + name = "com_github_docker_docker", + # Docker ships a pre-generated plugin.pb.go. Disabling proto rule + # generation makes gazelle use that checked-in Go source instead of + # recompiling the .proto via protoc, which would otherwise pull in the + # protobuf C++ toolchain. Mirrors the gazelle_override in the root + # MODULE.bazel for the bzlmod build. + build_directives = ["gazelle:proto disable_global"], + importpath = "github.com/docker/docker", + sum = "h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=", + version = "v28.5.2+incompatible", + ) + go_repository( + name = "com_github_docker_docker_credential_helpers", + importpath = "github.com/docker/docker-credential-helpers", + sum = "h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=", + version = "v0.9.3", + ) + go_repository( + name = "com_github_docker_go_connections", + importpath = "github.com/docker/go-connections", + sum = "h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c=", + version = "v0.7.0", + ) + go_repository( + name = "com_github_docker_go_events", + importpath = "github.com/docker/go-events", + sum = "h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=", + version = "v0.0.0-20190806004212-e31b211e4f1c", + ) + go_repository( + name = "com_github_docker_go_metrics", + importpath = "github.com/docker/go-metrics", + sum = "h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=", + version = "v0.0.1", + ) + go_repository( + name = "com_github_docker_go_units", + importpath = "github.com/docker/go-units", + sum = "h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=", + version = "v0.5.0", + ) + go_repository( + name = "com_github_emicklei_go_restful_v3", + importpath = "github.com/emicklei/go-restful/v3", + sum = "h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ=", + version = "v3.10.1", + ) + go_repository( + name = "com_github_fatih_color", + importpath = "github.com/fatih/color", + sum = "h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=", + version = "v1.15.0", + ) + go_repository( + name = "com_github_felixge_httpsnoop", + importpath = "github.com/felixge/httpsnoop", + sum = "h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=", + version = "v1.0.4", + ) + go_repository( + name = "com_github_fsnotify_fsnotify", + importpath = "github.com/fsnotify/fsnotify", + sum = "h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=", + version = "v1.6.0", + ) + go_repository( + name = "com_github_fsouza_go_dockerclient", + importpath = "github.com/fsouza/go-dockerclient", + sum = "h1:u+jAOuR9TZ3PAx2pdHA+ALt1ZZhS8Qx+A4d964IqXtw=", + version = "v1.13.2", + ) + go_repository( + name = "com_github_go_jose_go_jose_v3", + importpath = "github.com/go-jose/go-jose/v3", + sum = "h1:BLLJWbC4nMZOfuPVxoZIxeYsn6Nl2r1fITaJ78UQlVQ=", + version = "v3.0.5", + ) + go_repository( + name = "com_github_go_logr_logr", + importpath = "github.com/go-logr/logr", + sum = "h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=", + version = "v1.4.3", + ) + go_repository( + name = "com_github_go_logr_stdr", + importpath = "github.com/go-logr/stdr", + sum = "h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=", + version = "v1.2.2", + ) + go_repository( + name = "com_github_godbus_dbus_v5", + importpath = "github.com/godbus/dbus/v5", + sum = "h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=", + version = "v5.1.0", + ) + go_repository( + name = "com_github_gogo_protobuf", + importpath = "github.com/gogo/protobuf", + sum = "h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=", + version = "v1.3.2", + ) + go_repository( + name = "com_github_golang_groupcache", + importpath = "github.com/golang/groupcache", + sum = "h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=", + version = "v0.0.0-20210331224755-41bb18bfe9da", + ) + go_repository( + name = "com_github_golang_protobuf", + importpath = "github.com/golang/protobuf", + sum = "h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=", + version = "v1.5.3", + ) + go_repository( + name = "com_github_google_go_cmp", + importpath = "github.com/google/go-cmp", + sum = "h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=", + version = "v0.7.0", + ) + go_repository( + name = "com_github_google_go_containerregistry", + importpath = "github.com/google/go-containerregistry", + sum = "h1:/vPFuVXDjtFREsVArW+0h1CIl5urnOhzei4X2DMW9IU=", + version = "v0.21.7", + ) + go_repository( + name = "com_github_google_gofuzz", + importpath = "github.com/google/gofuzz", + sum = "h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=", + version = "v1.2.0", + ) + go_repository( + name = "com_github_google_uuid", + importpath = "github.com/google/uuid", + sum = "h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=", + version = "v1.6.0", + ) + go_repository( + name = "com_github_gorilla_mux", + importpath = "github.com/gorilla/mux", + sum = "h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=", + version = "v1.8.1", + ) + go_repository( + name = "com_github_grpc_ecosystem_go_grpc_middleware", + importpath = "github.com/grpc-ecosystem/go-grpc-middleware", + sum = "h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=", + version = "v1.3.0", + ) + go_repository( + name = "com_github_grpc_ecosystem_go_grpc_prometheus", + importpath = "github.com/grpc-ecosystem/go-grpc-prometheus", + sum = "h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=", + version = "v1.2.0", + ) + go_repository( + name = "com_github_grpc_ecosystem_grpc_gateway_v2", + importpath = "github.com/grpc-ecosystem/grpc-gateway/v2", + sum = "h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=", + version = "v2.16.0", + ) + go_repository( + name = "com_github_hashicorp_errwrap", + importpath = "github.com/hashicorp/errwrap", + sum = "h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=", + version = "v1.1.0", + ) + go_repository( + name = "com_github_inconshreveable_mousetrap", + importpath = "github.com/inconshreveable/mousetrap", + sum = "h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=", + version = "v1.1.0", + ) + go_repository( + name = "com_github_intel_goresctrl", + importpath = "github.com/intel/goresctrl", + sum = "h1:kcDhjE3ZF/mNrJuRzLS3LY2Hp6atFaF1XVFBT7SVL2g=", + version = "v0.5.0", + ) + go_repository( + name = "com_github_joho_godotenv", + importpath = "github.com/joho/godotenv", + sum = "h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=", + version = "v1.5.1", + ) + go_repository( + name = "com_github_json_iterator_go", + importpath = "github.com/json-iterator/go", + sum = "h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=", + version = "v1.1.12", + ) + go_repository( + name = "com_github_keybase_go_keychain", + importpath = "github.com/keybase/go-keychain", + sum = "h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=", + version = "v0.0.1", + ) + + go_repository( + name = "com_github_kisielk_errcheck", + importpath = "github.com/kisielk/errcheck", + sum = "h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY=", + version = "v1.5.0", + ) + go_repository( + name = "com_github_kisielk_gotool", + importpath = "github.com/kisielk/gotool", + sum = "h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=", + version = "v1.0.0", + ) + go_repository( + name = "com_github_klauspost_compress", + importpath = "github.com/klauspost/compress", + sum = "h1:sXLILfc9jV2QYWkzFOPWStmcUVH2RHEB1JCdY2oVvCQ=", + version = "v1.19.0", + ) + go_repository( + name = "com_github_kr_pretty", + importpath = "github.com/kr/pretty", + sum = "h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=", + version = "v0.3.1", + ) + go_repository( + name = "com_github_kr_pty", + importpath = "github.com/kr/pty", + sum = "h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw=", + version = "v1.1.1", + ) + go_repository( + name = "com_github_kr_text", + importpath = "github.com/kr/text", + sum = "h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=", + version = "v0.2.0", + ) + go_repository( + name = "com_github_magefile_mage", + importpath = "github.com/magefile/mage", + sum = "h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=", + version = "v1.14.0", + ) + go_repository( + name = "com_github_mattn_go_colorable", + importpath = "github.com/mattn/go-colorable", + sum = "h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=", + version = "v0.1.13", + ) + go_repository( + name = "com_github_mattn_go_isatty", + importpath = "github.com/mattn/go-isatty", + sum = "h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=", + version = "v0.0.17", + ) + go_repository( + name = "com_github_matttproud_golang_protobuf_extensions", + importpath = "github.com/matttproud/golang_protobuf_extensions", + sum = "h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=", + version = "v1.0.4", + ) + go_repository( + name = "com_github_microsoft_go_winio", + importpath = "github.com/Microsoft/go-winio", + sum = "h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=", + version = "v0.6.2", + ) + go_repository( + name = "com_github_microsoft_hcsshim", + importpath = "github.com/Microsoft/hcsshim", + sum = "h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ=", + version = "v0.11.7", + ) + go_repository( + name = "com_github_miekg_pkcs11", + importpath = "github.com/miekg/pkcs11", + sum = "h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=", + version = "v1.1.1", + ) + go_repository( + name = "com_github_mistifyio_go_zfs_v3", + importpath = "github.com/mistifyio/go-zfs/v3", + sum = "h1:YaoXgBePoMA12+S1u/ddkv+QqxcfiZK4prI6HPnkFiU=", + version = "v3.0.1", + ) + go_repository( + name = "com_github_mitchellh_go_homedir", + importpath = "github.com/mitchellh/go-homedir", + sum = "h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=", + version = "v1.1.0", + ) + go_repository( + name = "com_github_moby_docker_image_spec", + importpath = "github.com/moby/docker-image-spec", + sum = "h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=", + version = "v1.3.1", + ) + go_repository( + name = "com_github_moby_go_archive", + importpath = "github.com/moby/go-archive", + sum = "h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8=", + version = "v0.2.0", + ) + + go_repository( + name = "com_github_moby_locker", + importpath = "github.com/moby/locker", + sum = "h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=", + version = "v1.0.1", + ) + go_repository( + name = "com_github_moby_moby_api", + importpath = "github.com/moby/moby/api", + sum = "h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc=", + version = "v1.55.0", + ) + go_repository( + name = "com_github_moby_moby_client", + importpath = "github.com/moby/moby/client", + sum = "h1:5XhyPk2fuOWf6RlSFa3MkIIgDZkF25xToXW8Q/BH7cc=", + version = "v0.5.0", + ) + go_repository( + name = "com_github_moby_patternmatcher", + importpath = "github.com/moby/patternmatcher", + sum = "h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U=", + version = "v0.6.1", + ) + go_repository( + name = "com_github_moby_spdystream", + importpath = "github.com/moby/spdystream", + sum = "h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y=", + version = "v0.5.1", + ) + go_repository( + name = "com_github_moby_sys_atomicwriter", + importpath = "github.com/moby/sys/atomicwriter", + sum = "h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=", + version = "v0.1.0", + ) + go_repository( + name = "com_github_moby_sys_mount", + importpath = "github.com/moby/sys/mount", + sum = "h1:yn5jq4STPztkkzSKpZkLcmjue+bZJ0u2AuQY1iNI1Ww=", + version = "v0.3.4", + ) + go_repository( + name = "com_github_moby_sys_mountinfo", + importpath = "github.com/moby/sys/mountinfo", + sum = "h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=", + version = "v0.7.2", + ) + go_repository( + name = "com_github_moby_sys_reexec", + importpath = "github.com/moby/sys/reexec", + sum = "h1:RrBi8e0EBTLEgfruBOFcxtElzRGTEUkeIFaVXgU7wok=", + version = "v0.1.0", + ) + go_repository( + name = "com_github_moby_sys_sequential", + importpath = "github.com/moby/sys/sequential", + sum = "h1:ASQNGNROJSuOO6LL6bPHbKvuZu6NU8P4ldPWk31zj/8=", + version = "v0.7.0", + ) + go_repository( + name = "com_github_moby_sys_signal", + importpath = "github.com/moby/sys/signal", + sum = "h1:25RW3d5TnQEoKvRbEKUGay6DCQ46IxAVTT9CUMgmsSI=", + version = "v0.7.0", + ) + go_repository( + name = "com_github_moby_sys_symlink", + importpath = "github.com/moby/sys/symlink", + sum = "h1:tk1rOM+Ljp0nFmfOIBtlV3rTDlWOwFRhjEeAhZB0nZc=", + version = "v0.2.0", + ) + go_repository( + name = "com_github_moby_sys_user", + importpath = "github.com/moby/sys/user", + sum = "h1:RgjRlaDKi/Xmyrz4t8lyzXT6v2ooFeO/7xtchmhVWE0=", + version = "v0.4.1", + ) + go_repository( + name = "com_github_moby_sys_userns", + importpath = "github.com/moby/sys/userns", + sum = "h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=", + version = "v0.1.0", + ) + go_repository( + name = "com_github_moby_term", + importpath = "github.com/moby/term", + sum = "h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=", + version = "v0.5.2", + ) + go_repository( + name = "com_github_modern_go_concurrent", + importpath = "github.com/modern-go/concurrent", + sum = "h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=", + version = "v0.0.0-20180306012644-bacd9c7ef1dd", + ) + go_repository( + name = "com_github_modern_go_reflect2", + importpath = "github.com/modern-go/reflect2", + sum = "h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=", + version = "v1.0.2", + ) + go_repository( + name = "com_github_morikuni_aec", + importpath = "github.com/morikuni/aec", + sum = "h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=", + version = "v1.0.0", + ) + go_repository( + name = "com_github_opencontainers_go_digest", + importpath = "github.com/opencontainers/go-digest", + sum = "h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=", + version = "v1.0.0", + ) + go_repository( + name = "com_github_opencontainers_image_spec", + importpath = "github.com/opencontainers/image-spec", + sum = "h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=", + version = "v1.1.1", + ) + go_repository( + name = "com_github_opencontainers_runtime_spec", + importpath = "github.com/opencontainers/runtime-spec", + sum = "h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg=", + version = "v1.1.0", + ) + go_repository( + name = "com_github_opencontainers_runtime_tools", + importpath = "github.com/opencontainers/runtime-tools", + sum = "h1:DmNGcqH3WDbV5k8OJ+esPWbqUOX5rMLR2PMvziDMJi0=", + version = "v0.9.1-0.20221107090550-2e043c6bd626", + ) + go_repository( + name = "com_github_opencontainers_selinux", + importpath = "github.com/opencontainers/selinux", + sum = "h1:A8nNeceYngH9Ow++M+VVEwJVpdFmrlxsN22F+ISDCJE=", + version = "v1.13.1", + ) + go_repository( + name = "com_github_pelletier_go_toml", + importpath = "github.com/pelletier/go-toml", + sum = "h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=", + version = "v1.9.5", + ) + go_repository( + name = "com_github_pkg_errors", + importpath = "github.com/pkg/errors", + sum = "h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=", + version = "v0.9.1", + ) + go_repository( + name = "com_github_pmezard_go_difflib", + importpath = "github.com/pmezard/go-difflib", + sum = "h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=", + version = "v1.0.0", + ) + go_repository( + name = "com_github_prometheus_client_golang", + importpath = "github.com/prometheus/client_golang", + sum = "h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=", + version = "v1.16.0", + ) + go_repository( + name = "com_github_prometheus_client_model", + importpath = "github.com/prometheus/client_model", + sum = "h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=", + version = "v0.3.0", + ) + go_repository( + name = "com_github_prometheus_common", + importpath = "github.com/prometheus/common", + sum = "h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=", + version = "v0.42.0", + ) + go_repository( + name = "com_github_prometheus_procfs", + importpath = "github.com/prometheus/procfs", + sum = "h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=", + version = "v0.10.1", + ) + go_repository( + name = "com_github_rogpeppe_go_internal", + importpath = "github.com/rogpeppe/go-internal", + sum = "h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=", + version = "v1.14.1", + ) + go_repository( + name = "com_github_russross_blackfriday", + importpath = "github.com/russross/blackfriday", + sum = "h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=", + version = "v1.6.0", + ) + go_repository( + name = "com_github_russross_blackfriday_v2", + importpath = "github.com/russross/blackfriday/v2", + sum = "h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=", + version = "v2.1.0", + ) + go_repository( + name = "com_github_santhosh_tekuri_jsonschema_v5", + importpath = "github.com/santhosh-tekuri/jsonschema/v5", + sum = "h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=", + version = "v5.3.1", + ) + go_repository( + name = "com_github_sirupsen_logrus", + importpath = "github.com/sirupsen/logrus", + sum = "h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=", + version = "v1.9.4", + ) + go_repository( + name = "com_github_spf13_cobra", + importpath = "github.com/spf13/cobra", + sum = "h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=", + version = "v1.10.2", + ) + go_repository( + name = "com_github_spf13_pflag", + importpath = "github.com/spf13/pflag", + sum = "h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=", + version = "v1.0.10", + ) + go_repository( + name = "com_github_stefanberger_go_pkcs11uri", + importpath = "github.com/stefanberger/go-pkcs11uri", + sum = "h1:pnnLyeX7o/5aX8qUQ69P/mLojDqwda8hFOCBTmP/6hw=", + version = "v0.0.0-20230803200340-78284954bff6", + ) + go_repository( + name = "com_github_stretchr_testify", + importpath = "github.com/stretchr/testify", + sum = "h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=", + version = "v1.11.1", + ) + go_repository( + name = "com_github_syndtr_gocapability", + importpath = "github.com/syndtr/gocapability", + sum = "h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=", + version = "v0.0.0-20200815063812-42c35b437635", + ) + go_repository( + name = "com_github_tchap_go_patricia_v2", + importpath = "github.com/tchap/go-patricia/v2", + sum = "h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes=", + version = "v2.3.1", + ) + go_repository( + name = "com_github_urfave_cli", + importpath = "github.com/urfave/cli", + sum = "h1:MH0k6uJxdwdeWQTwhSO42Pwr4YLrNLwBtg1MRgTqPdQ=", + version = "v1.22.16", + ) + go_repository( + name = "com_github_vbatts_tar_split", + importpath = "github.com/vbatts/tar-split", + sum = "h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4=", + version = "v0.12.2", + ) + go_repository( + name = "com_github_vishvananda_netlink", + importpath = "github.com/vishvananda/netlink", + sum = "h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=", + version = "v1.2.1-beta.2", + ) + go_repository( + name = "com_github_vishvananda_netns", + importpath = "github.com/vishvananda/netns", + sum = "h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA=", + version = "v0.0.0-20210104183010-2eb08e3e575f", + ) + go_repository( + name = "com_github_yuin_goldmark", + importpath = "github.com/yuin/goldmark", + sum = "h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=", + version = "v1.2.1", + ) + go_repository( + name = "com_google_cloud_go_compute", + importpath = "cloud.google.com/go/compute", + sum = "h1:DcTwsFgGev/wV5+q8o2fzgcHOaac+DKGC91ZlvpsQds=", + version = "v1.19.3", + ) + go_repository( + name = "com_google_cloud_go_compute_metadata", + importpath = "cloud.google.com/go/compute/metadata", + sum = "h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=", + version = "v0.7.0", + ) + go_repository( + name = "in_gopkg_check_v1", + importpath = "gopkg.in/check.v1", + sum = "h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=", + version = "v1.0.0-20201130134442-10cb98267c6c", + ) + go_repository( + name = "in_gopkg_inf_v0", + importpath = "gopkg.in/inf.v0", + sum = "h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=", + version = "v0.9.1", + ) + go_repository( + name = "in_gopkg_yaml_v2", + importpath = "gopkg.in/yaml.v2", + sum = "h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=", + version = "v2.4.0", + ) + go_repository( + name = "in_gopkg_yaml_v3", + importpath = "gopkg.in/yaml.v3", + sum = "h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=", + version = "v3.0.1", + ) + go_repository( + name = "in_yaml_go_yaml_v3", + importpath = "go.yaml.in/yaml/v3", + sum = "h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=", + version = "v3.0.4", + ) + go_repository( + name = "io_cncf_tags_container_device_interface", + importpath = "tags.cncf.io/container-device-interface", + sum = "h1:c0jN4Mt6781jD67NdPajmZlD1qrqQyov/Xfoab37lj0=", + version = "v0.8.1", + ) + go_repository( + name = "io_cncf_tags_container_device_interface_specs_go", + importpath = "tags.cncf.io/container-device-interface/specs-go", + sum = "h1:QYGFzGxvYK/ZLMrjhvY0RjpUavIn4KcmRmVP/JjdBTA=", + version = "v0.8.0", + ) + go_repository( + name = "io_etcd_go_bbolt", + importpath = "go.etcd.io/bbolt", + sum = "h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=", + version = "v1.3.10", + ) + go_repository( + name = "io_k8s_api", + importpath = "k8s.io/api", + sum = "h1:dM3cinp3PGB6asOySalOZxEG4CZ0IAdJsrYZXE/ovGQ=", + version = "v0.26.2", + ) + go_repository( + name = "io_k8s_apimachinery", + importpath = "k8s.io/apimachinery", + sum = "h1:CdxflD4AF61yewuid0fLl6bM4a3q04jWel0IlP+aYjs=", + version = "v0.27.4", + ) + go_repository( + name = "io_k8s_apiserver", + importpath = "k8s.io/apiserver", + sum = "h1:Pk8lmX4G14hYqJd1poHGC08G03nIHVqdJMR0SD3IH3o=", + version = "v0.26.2", + ) + go_repository( + name = "io_k8s_client_go", + importpath = "k8s.io/client-go", + sum = "h1:s1WkVujHX3kTp4Zn4yGNFK+dlDXy1bAAkIl+cFAiuYI=", + version = "v0.26.2", + ) + go_repository( + name = "io_k8s_component_base", + importpath = "k8s.io/component-base", + sum = "h1:IfWgCGUDzrD6wLLgXEstJKYZKAFS2kO+rBRi0p3LqcI=", + version = "v0.26.2", + ) + go_repository( + name = "io_k8s_cri_api", + importpath = "k8s.io/cri-api", + sum = "h1:KWO+U8MfI9drXB/P4oU9VchaWYOlwDglJZVHWMpTT3Q=", + version = "v0.27.1", + ) + go_repository( + name = "io_k8s_klog_v2", + importpath = "k8s.io/klog/v2", + sum = "h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw=", + version = "v2.90.1", + ) + go_repository( + name = "io_k8s_sigs_json", + importpath = "sigs.k8s.io/json", + sum = "h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=", + version = "v0.0.0-20221116044647-bc3834ca7abd", + ) + go_repository( + name = "io_k8s_sigs_structured_merge_diff_v4", + importpath = "sigs.k8s.io/structured-merge-diff/v4", + sum = "h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=", + version = "v4.2.3", + ) + go_repository( + name = "io_k8s_sigs_yaml", + importpath = "sigs.k8s.io/yaml", + sum = "h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=", + version = "v1.3.0", + ) + go_repository( + name = "io_k8s_utils", + importpath = "k8s.io/utils", + sum = "h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk=", + version = "v0.0.0-20230220204549-a5ecb0141aa5", + ) + go_repository( + name = "io_opencensus_go", + importpath = "go.opencensus.io", + sum = "h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=", + version = "v0.24.0", + ) + go_repository( + name = "io_opentelemetry_go_auto_sdk", + importpath = "go.opentelemetry.io/auto/sdk", + sum = "h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=", + version = "v1.2.1", + ) + + go_repository( + name = "io_opentelemetry_go_contrib_instrumentation_google_golang_org_grpc_otelgrpc", + importpath = "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc", + sum = "h1:SpGay3w+nEwMpfVnbqOLH5gY52/foP8RE8UzTZ1pdSE=", + version = "v0.46.1", + ) + go_repository( + name = "io_opentelemetry_go_contrib_instrumentation_net_http_otelhttp", + importpath = "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp", + sum = "h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=", + version = "v0.61.0", + ) + go_repository( + name = "io_opentelemetry_go_otel", + importpath = "go.opentelemetry.io/otel", + sum = "h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c=", + version = "v1.41.0", + ) + go_repository( + name = "io_opentelemetry_go_otel_exporters_otlp_otlptrace", + importpath = "go.opentelemetry.io/otel/exporters/otlp/otlptrace", + sum = "h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=", + version = "v1.19.0", + ) + go_repository( + name = "io_opentelemetry_go_otel_exporters_otlp_otlptrace_otlptracegrpc", + importpath = "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc", + sum = "h1:3d+S281UTjM+AbF31XSOYn1qXn3BgIdWl8HNEpx08Jk=", + version = "v1.19.0", + ) + go_repository( + name = "io_opentelemetry_go_otel_exporters_otlp_otlptrace_otlptracehttp", + importpath = "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp", + sum = "h1:wpMfgF8E1rkrT1Z6meFh1NDtownE9Ii3n3X2GJYjsaU=", + version = "v1.33.0", + ) + go_repository( + name = "io_opentelemetry_go_otel_metric", + importpath = "go.opentelemetry.io/otel/metric", + sum = "h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ=", + version = "v1.41.0", + ) + go_repository( + name = "io_opentelemetry_go_otel_sdk", + importpath = "go.opentelemetry.io/otel/sdk", + sum = "h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=", + version = "v1.36.0", + ) + go_repository( + name = "io_opentelemetry_go_otel_sdk_metric", + importpath = "go.opentelemetry.io/otel/sdk/metric", + sum = "h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=", + version = "v1.36.0", + ) + go_repository( + name = "io_opentelemetry_go_otel_trace", + importpath = "go.opentelemetry.io/otel/trace", + sum = "h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0=", + version = "v1.41.0", + ) + go_repository( + name = "io_opentelemetry_go_proto_otlp", + importpath = "go.opentelemetry.io/proto/otlp", + sum = "h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=", + version = "v1.0.0", + ) + go_repository( + name = "net_pgregory_rapid", + importpath = "pgregory.net/rapid", + sum = "h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=", + version = "v1.2.0", + ) + + go_repository( + name = "org_golang_google_appengine", + importpath = "google.golang.org/appengine", + sum = "h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=", + version = "v1.6.7", + ) + go_repository( + name = "org_golang_google_genproto", + importpath = "google.golang.org/genproto", + sum = "h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=", + version = "v0.0.0-20231106174013-bbf56f31fb17", + ) + go_repository( + name = "org_golang_google_genproto_googleapis_api", + importpath = "google.golang.org/genproto/googleapis/api", + sum = "h1:2yNACc1O40tTnrsbk9Cv6oxiW8pxI/pXj0wRtdlYmgY=", + version = "v0.0.0-20231120223509-83a465c0220f", + ) + go_repository( + name = "org_golang_google_genproto_googleapis_rpc", + importpath = "google.golang.org/genproto/googleapis/rpc", + sum = "h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=", + version = "v0.0.0-20240903143218-8af14fe29dc1", + ) + go_repository( + name = "org_golang_google_grpc", + importpath = "google.golang.org/grpc", + sum = "h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw=", + version = "v1.67.0", + ) + go_repository( + name = "org_golang_google_protobuf", + importpath = "google.golang.org/protobuf", + sum = "h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=", + version = "v1.34.2", + ) + go_repository( + name = "org_golang_x_crypto", + importpath = "golang.org/x/crypto", + sum = "h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=", + version = "v0.47.0", + ) + go_repository( + name = "org_golang_x_mod", + importpath = "golang.org/x/mod", + sum = "h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=", + version = "v0.37.0", + ) + go_repository( + name = "org_golang_x_net", + importpath = "golang.org/x/net", + sum = "h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=", + version = "v0.48.0", + ) + go_repository( + name = "org_golang_x_oauth2", + importpath = "golang.org/x/oauth2", + sum = "h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=", + version = "v0.36.0", + ) + go_repository( + name = "org_golang_x_sync", + importpath = "golang.org/x/sync", + sum = "h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=", + version = "v0.21.0", + ) + go_repository( + name = "org_golang_x_sys", + importpath = "golang.org/x/sys", + sum = "h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=", + version = "v0.46.0", + ) + go_repository( + name = "org_golang_x_term", + importpath = "golang.org/x/term", + sum = "h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=", + version = "v0.43.0", + ) + go_repository( + name = "org_golang_x_text", + importpath = "golang.org/x/text", + sum = "h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=", + version = "v0.33.0", + ) + go_repository( + name = "org_golang_x_time", + importpath = "golang.org/x/time", + sum = "h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=", + version = "v0.11.0", + ) + go_repository( + name = "org_golang_x_tools", + importpath = "golang.org/x/tools", + sum = "h1:7jTurBkPZu4moS/Uy4OQT1M+QBlsj3wejyZwsT8Z7rk=", + version = "v0.46.0", + ) + go_repository( + name = "org_golang_x_xerrors", + importpath = "golang.org/x/xerrors", + sum = "h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=", + version = "v0.0.0-20200804184101-5ec99f83aff1", + ) + go_repository( + name = "org_mozilla_go_pkcs7", + importpath = "go.mozilla.org/pkcs7", + sum = "h1:A/5uWzF44DlIgdm/PQFwfMkW0JX+cIcQi/SwLAmZP5M=", + version = "v0.0.0-20200128120323-432b2356ecb1", + ) + go_repository( + name = "tools_gotest_v3", + importpath = "gotest.tools/v3", + sum = "h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=", + version = "v3.5.2", + ) diff --git a/bazel/test/test.yaml b/bazel/test/test.yaml index 6e1013ad..936b333e 100644 --- a/bazel/test/test.yaml +++ b/bazel/test/test.yaml @@ -6,3 +6,8 @@ metadataTest: value: "/test" entrypoint: ["/custom_bin"] cmd: ["--arg1", "--arg2"] + +containerRunOptions: + sysctls: + net.core.somaxconn: "1024" + net.ipv4.tcp_max_syn_backlog: "4096" diff --git a/cmd/container-structure-test/BUILD.bazel b/cmd/container-structure-test/BUILD.bazel new file mode 100644 index 00000000..3b3cb8b3 --- /dev/null +++ b/cmd/container-structure-test/BUILD.bazel @@ -0,0 +1,18 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "container-structure-test_lib", + srcs = ["container-structure-test.go"], + importpath = "github.com/GoogleContainerTools/container-structure-test/cmd/container-structure-test", + visibility = ["//visibility:private"], + deps = [ + "//cmd/container-structure-test/app", + "@com_github_sirupsen_logrus//:logrus", + ], +) + +go_binary( + name = "container-structure-test", + embed = [":container-structure-test_lib"], + visibility = ["//visibility:public"], +) diff --git a/cmd/container-structure-test/app/BUILD.bazel b/cmd/container-structure-test/app/BUILD.bazel new file mode 100644 index 00000000..91fb2064 --- /dev/null +++ b/cmd/container-structure-test/app/BUILD.bazel @@ -0,0 +1,9 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "app", + srcs = ["container-structure-test.go"], + importpath = "github.com/GoogleContainerTools/container-structure-test/cmd/container-structure-test/app", + visibility = ["//visibility:public"], + deps = ["//cmd/container-structure-test/app/cmd"], +) diff --git a/cmd/container-structure-test/app/cmd/BUILD.bazel b/cmd/container-structure-test/app/cmd/BUILD.bazel new file mode 100644 index 00000000..11d1d546 --- /dev/null +++ b/cmd/container-structure-test/app/cmd/BUILD.bazel @@ -0,0 +1,31 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "cmd", + srcs = [ + "cmd.go", + "test.go", + "version.go", + ], + importpath = "github.com/GoogleContainerTools/container-structure-test/cmd/container-structure-test/app/cmd", + visibility = ["//visibility:public"], + deps = [ + "//cmd/container-structure-test/app/cmd/test", + "//cmd/container-structure-test/app/flags", + "//internal/pkgutil", + "//pkg/color", + "//pkg/config", + "//pkg/drivers", + "//pkg/output", + "//pkg/types/unversioned", + "//pkg/utils", + "//pkg/version", + "@com_github_fsouza_go_dockerclient//:go-dockerclient", + "@com_github_google_go_containerregistry//pkg/name", + "@com_github_google_go_containerregistry//pkg/v1/daemon", + "@com_github_opencontainers_image_spec//specs-go/v1:specs-go", + "@com_github_pkg_errors//:errors", + "@com_github_sirupsen_logrus//:logrus", + "@com_github_spf13_cobra//:cobra", + ], +) diff --git a/cmd/container-structure-test/app/cmd/test.go b/cmd/container-structure-test/app/cmd/test.go index 069b1464..d7bf3168 100644 --- a/cmd/container-structure-test/app/cmd/test.go +++ b/cmd/container-structure-test/app/cmd/test.go @@ -21,6 +21,7 @@ import ( "runtime" "github.com/GoogleContainerTools/container-structure-test/cmd/container-structure-test/app/cmd/test" + "github.com/GoogleContainerTools/container-structure-test/internal/pkgutil" v1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/GoogleContainerTools/container-structure-test/pkg/color" @@ -33,7 +34,6 @@ import ( docker "github.com/fsouza/go-dockerclient" "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1/daemon" - "github.com/google/go-containerregistry/pkg/v1/layout" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -60,20 +60,30 @@ func NewCmdTest(out io.Writer) *cobra.Command { return test.ValidateArgs(opts) }, RunE: func(cmd *cobra.Command, _ []string) error { - if opts.TestReport != "" { - // Force JsonOutput - if opts.Output == unversioned.Text { - opts.JSON = true - opts.Output = unversioned.Json + // Open test report file if specified + var reportOut io.Writer + reportFormatFlag := cmd.Flags().Lookup("test-report-format") - logrus.Warn("raw text format unsupported for writing output file, defaulting to JSON") + if opts.TestReport != "" { + // Validate report format - only json and junit are supported + if opts.TestReportFormat == unversioned.Text { + if reportFormatFlag.Changed { + // User explicitly set --test-report-format text, which is not supported + return fmt.Errorf("--test-report-format does not support 'text'; use 'json' or 'junit'") + } + // Default to JSON for backward compatibility (Text is zero value when flag not set) + opts.TestReportFormat = unversioned.Json } + testReportFile, err := os.Create(opts.TestReport) if err != nil { return err } - rootCmd.SetOutput(testReportFile) - out = testReportFile // override writer + defer testReportFile.Close() + reportOut = testReportFile + } else if reportFormatFlag.Changed { + // User specified --test-report-format without --test-report + return fmt.Errorf("--test-report-format requires --test-report to be specified") } if opts.Quiet { @@ -86,7 +96,7 @@ func NewCmdTest(out io.Writer) *cobra.Command { opts.Output = unversioned.Json } - return run(out) + return run(out, reportOut) }, } @@ -94,7 +104,7 @@ func NewCmdTest(out io.Writer) *cobra.Command { return testCmd } -func run(out io.Writer) error { +func run(out, reportOut io.Writer) error { args = &drivers.DriverConfig{ Image: opts.ImagePath, Save: opts.Save, @@ -106,66 +116,51 @@ func run(out io.Writer) error { var err error if opts.ImageFromLayout != "" { - if opts.Driver != drivers.Docker { - logrus.Fatal("--image-from-oci-layout is not supported when not using Docker driver") - } - l, err := layout.ImageIndexFromPath(opts.ImageFromLayout) - if err != nil { - logrus.Fatalf("loading %s as OCI layout: %v", opts.ImageFromLayout, err) - } - m, err := l.IndexManifest() - if err != nil { - logrus.Fatalf("could not read OCI index manifest %s: %v", opts.ImageFromLayout, err) + if opts.Driver != drivers.Docker && opts.Driver != drivers.Tar { + logrus.Fatal("--image-from-oci-layout is only supported with Docker or Tar drivers") } - if len(m.Manifests) != 1 { - logrus.Fatalf("OCI layout contains %d entries. expected only one", len(m.Manifests)) - } - - desc := m.Manifests[0] - - if desc.MediaType.IsIndex() { - logrus.Fatal("multi-arch images are not supported yet.") - } - - img, err := l.Image(desc.Digest) - - if err != nil { - logrus.Fatalf("could not get image from %s: %v", opts.ImageFromLayout, err) - } + if opts.Driver == drivers.Tar { + args.OCILayout = opts.ImageFromLayout + } else { + img, desc, err := pkgutil.ImageFromOCILayout(opts.ImageFromLayout, opts.Platform) + if err != nil { + logrus.Fatalf("loading OCI layout %s: %v", opts.ImageFromLayout, err) + } - var tag name.Tag + var tag name.Tag - ref := desc.Annotations[v1.AnnotationRefName] - if ref != "" && !opts.IgnoreRefAnnotation { - tag, err = name.NewTag(ref) - if err != nil { - logrus.Fatalf("could not parse ref annotation %s: %v", v1.AnnotationRefName, err) + ref := desc.Annotations[v1.AnnotationRefName] + if ref != "" && !opts.IgnoreRefAnnotation { + tag, err = name.NewTag(ref) + if err != nil { + logrus.Fatalf("could not parse ref annotation %s: %v", v1.AnnotationRefName, err) + } + } else { + if opts.DefaultImageTag == "" { + logrus.Fatalf("index does not contain a reference annotation. --default-image-tag must be provided.") + } + tag, err = name.NewTag(opts.DefaultImageTag, name.StrictValidation) + if err != nil { + logrus.Fatalf("could parse the default image tag %s: %v", opts.DefaultImageTag, err) + } } - } else { - if opts.DefaultImageTag == "" { - logrus.Fatalf("index does not contain a reference annotation. --default-image-tag must be provided.") + var r string + if r, err = daemon.Write(tag, img); err != nil { + logrus.Fatalf("error loading oci layout into daemon:, %v", err) } - tag, err = name.NewTag(opts.DefaultImageTag, name.StrictValidation) + // For some reason, daemon.Write doesn't return errors for some edge cases. + // We should always print what the daemon sent back so that errors are transparent. + fmt.Println("Loaded ", tag.String(), r) + + _, err = daemon.Image(tag) if err != nil { - logrus.Fatalf("could parse the default image tag %s: %v", opts.DefaultImageTag, err) + logrus.Fatalf("error loading oci layout into daemon: %v", err) } - } - var r string - if r, err = daemon.Write(tag, img); err != nil { - logrus.Fatalf("error loading oci layout into daemon: %v, %s", err) - } - // For some reason, daemon.Write doesn't return errors for some edge cases. - // We should always print what the daemon sent back so that errors are transparent. - fmt.Println("Loaded ", tag.String(), r) - _, err = daemon.Image(tag) - if err != nil { - logrus.Fatalf("error loading oci layout into daemon: %v", err) + opts.ImagePath = tag.String() + args.Image = tag.String() } - - opts.ImagePath = tag.String() - args.Image = tag.String() } if opts.Pull { @@ -205,7 +200,7 @@ func run(out io.Writer) error { channel := make(chan interface{}, 1) go runTests(out, channel, args, driverImpl) // TODO(nkubala): put a sync.WaitGroup here - return test.ProcessResults(out, opts.Output, opts.JunitSuiteName, channel) + return test.ProcessResults(out, reportOut, opts.Output, opts.TestReportFormat, opts.JunitSuiteName, channel) } func runTests(out io.Writer, channel chan interface{}, args *drivers.DriverConfig, driverImpl func(drivers.DriverConfig) (drivers.Driver, error)) { @@ -250,5 +245,7 @@ func AddTestFlags(cmd *cobra.Command) { cmd.Flags().StringArrayVarP(&opts.ConfigFiles, "config", "c", []string{}, "test config files") cmd.MarkFlagRequired("config") - cmd.Flags().StringVar(&opts.TestReport, "test-report", "", "generate test report and write it to specified file (supported format: json, junit; default: json)") + cmd.Flags().StringVar(&opts.TestReport, "test-report", "", "generate test report and write it to specified file") + cmd.Flags().VarP(&opts.TestReportFormat, "test-report-format", "", "format for the test report file (json, junit)") + cmd.Flags().Lookup("test-report-format").DefValue = "json" } diff --git a/cmd/container-structure-test/app/cmd/test/BUILD.bazel b/cmd/container-structure-test/app/cmd/test/BUILD.bazel new file mode 100644 index 00000000..153cdf18 --- /dev/null +++ b/cmd/container-structure-test/app/cmd/test/BUILD.bazel @@ -0,0 +1,24 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "test", + srcs = ["util.go"], + importpath = "github.com/GoogleContainerTools/container-structure-test/cmd/container-structure-test/app/cmd/test", + visibility = ["//visibility:public"], + deps = [ + "//pkg/config", + "//pkg/drivers", + "//pkg/output", + "//pkg/types", + "//pkg/types/unversioned", + "@com_github_pkg_errors//:errors", + "@in_gopkg_yaml_v2//:yaml_v2", + ], +) + +go_test( + name = "test_test", + srcs = ["util_test.go"], + embed = [":test"], + deps = ["//pkg/types/unversioned"], +) diff --git a/cmd/container-structure-test/app/cmd/test/util.go b/cmd/container-structure-test/app/cmd/test/util.go index ba912e34..83113f88 100644 --- a/cmd/container-structure-test/app/cmd/test/util.go +++ b/cmd/container-structure-test/app/cmd/test/util.go @@ -105,7 +105,14 @@ func Parse(fp string, args *drivers.DriverConfig, driverImpl func(drivers.Driver return tests, nil } -func ProcessResults(out io.Writer, format unversioned.OutputValue, junitSuiteName string, c chan interface{}) error { +// ProcessResults processes test results and writes output to the appropriate destinations. +// - out: primary output writer (stdout) - format controlled by the format parameter +// - reportOut: optional test report writer - format controlled by reportFormat parameter +// - format: output format for the primary output (text, json, or junit) +// - reportFormat: output format for the report file (json or junit) +// - junitSuiteName: name for the JUnit test suite +// - c: channel of test results +func ProcessResults(out, reportOut io.Writer, format, reportFormat unversioned.OutputValue, junitSuiteName string, c chan interface{}) error { totalPass := 0 totalFail := 0 totalDuration := time.Duration(0) @@ -116,7 +123,7 @@ func ProcessResults(out io.Writer, format unversioned.OutputValue, junitSuiteNam } for _, r := range results { if format == unversioned.Text { - // output individual results if we're not in json mode + // output individual results if we're in text mode output.OutputResult(out, r) } if r.IsPass() { @@ -130,7 +137,7 @@ func ProcessResults(out io.Writer, format unversioned.OutputValue, junitSuiteNam errStrings = append(errStrings, "FAIL") } if len(errStrings) > 0 { - err = fmt.Errorf(strings.Join(errStrings, "\n")) + err = fmt.Errorf("%s", strings.Join(errStrings, "\n")) } summary := unversioned.SummaryObject{ @@ -139,11 +146,20 @@ func ProcessResults(out io.Writer, format unversioned.OutputValue, junitSuiteNam Fail: totalFail, Duration: totalDuration, } - if format == unversioned.Json || format == unversioned.Junit { - // only output results here if we're in json mode - summary.Results = results + + summary.Results = results + + // Write final summary to primary output (stdout) + if outputErr := output.FinalResults(out, format, junitSuiteName, summary); outputErr != nil { + return outputErr + } + + // If a test report file is specified, write to it using the report format + if reportOut != nil { + if reportErr := output.FinalResults(reportOut, reportFormat, junitSuiteName, summary); reportErr != nil { + return reportErr + } } - output.FinalResults(out, format, junitSuiteName, summary) return err } diff --git a/cmd/container-structure-test/app/cmd/test/util_test.go b/cmd/container-structure-test/app/cmd/test/util_test.go new file mode 100644 index 00000000..3c4130bd --- /dev/null +++ b/cmd/container-structure-test/app/cmd/test/util_test.go @@ -0,0 +1,84 @@ +// Copyright 2018 Google Inc. All rights reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package test + +import ( + "bytes" + "encoding/json" + "strings" + "testing" + + "github.com/GoogleContainerTools/container-structure-test/pkg/types/unversioned" +) + +func resultsChannel(results ...*unversioned.TestResult) chan interface{} { + c := make(chan interface{}, len(results)) + for _, r := range results { + c <- r + } + close(c) + return c +} + +// ProcessResults must write a human-readable summary to the primary writer +// (text format) while writing a machine-readable report to reportOut, so a +// report file no longer silences stdout. +func TestProcessResultsDualOutput(t *testing.T) { + var stdout, report bytes.Buffer + c := resultsChannel(&unversioned.TestResult{Name: "passing", Pass: true}) + + if err := ProcessResults(&stdout, &report, unversioned.Text, unversioned.Json, "", c); err != nil { + t.Fatalf("ProcessResults returned error: %v", err) + } + + stdoutStr := stdout.String() + if !strings.Contains(stdoutStr, "RESULTS") || !strings.Contains(stdoutStr, "PASS") { + t.Errorf("primary stdout missing human-readable summary, got:\n%s", stdoutStr) + } + if strings.HasPrefix(strings.TrimSpace(stdoutStr), "{") { + t.Errorf("primary stdout should be text, not JSON, got:\n%s", stdoutStr) + } + + var summary unversioned.SummaryObject + if err := json.Unmarshal(report.Bytes(), &summary); err != nil { + t.Fatalf("report output is not valid JSON: %v\ngot:\n%s", err, report.String()) + } + if summary.Pass != 1 || summary.Total != 1 { + t.Errorf("report summary = %+v, want Pass=1 Total=1", summary) + } +} + +// When no report writer is supplied, only the primary writer is used and the +// summary still reflects the results. +func TestProcessResultsNoReport(t *testing.T) { + var stdout bytes.Buffer + c := resultsChannel( + &unversioned.TestResult{Name: "passing", Pass: true}, + &unversioned.TestResult{Name: "failing", Pass: false}, + ) + + err := ProcessResults(&stdout, nil, unversioned.Json, unversioned.Json, "", c) + if err == nil { + t.Fatal("expected non-nil error when a test fails") + } + + var summary unversioned.SummaryObject + if jsonErr := json.Unmarshal(stdout.Bytes(), &summary); jsonErr != nil { + t.Fatalf("primary output is not valid JSON: %v\ngot:\n%s", jsonErr, stdout.String()) + } + if summary.Pass != 1 || summary.Fail != 1 || summary.Total != 2 { + t.Errorf("summary = %+v, want Pass=1 Fail=1 Total=2", summary) + } +} diff --git a/cmd/container-structure-test/app/flags/BUILD.bazel b/cmd/container-structure-test/app/flags/BUILD.bazel new file mode 100644 index 00000000..cf2f7ff4 --- /dev/null +++ b/cmd/container-structure-test/app/flags/BUILD.bazel @@ -0,0 +1,9 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "flags", + srcs = ["template.go"], + importpath = "github.com/GoogleContainerTools/container-structure-test/cmd/container-structure-test/app/flags", + visibility = ["//visibility:public"], + deps = ["@com_github_pkg_errors//:errors"], +) diff --git a/defs.bzl b/defs.bzl index 2cecbc81..1154a636 100644 --- a/defs.bzl +++ b/defs.bzl @@ -27,7 +27,6 @@ https://github.com/GoogleContainerTools/container-structure-test#running-file-te test = True, toolchains = [ "@aspect_bazel_lib//lib:jq_toolchain_type", - "@bazel_tools//tools/sh:toolchain_type", "@container_structure_test//bazel:structure_test_toolchain_type", ], ) diff --git a/go.mod b/go.mod index 71e76785..d8e66d31 100644 --- a/go.mod +++ b/go.mod @@ -1,58 +1,56 @@ module github.com/GoogleContainerTools/container-structure-test -go 1.22 +go 1.25.5 require ( - github.com/fsouza/go-dockerclient v1.11.2 - github.com/google/go-cmp v0.6.0 - github.com/google/go-containerregistry v0.20.1 + github.com/fsouza/go-dockerclient v1.13.2 + github.com/google/go-cmp v0.7.0 + github.com/google/go-containerregistry v0.21.7 github.com/joho/godotenv v1.5.1 - github.com/moby/sys/sequential v0.6.0 - github.com/opencontainers/image-spec v1.1.0 + github.com/moby/sys/sequential v0.7.0 + github.com/opencontainers/image-spec v1.1.1 github.com/pkg/errors v0.9.1 - github.com/sirupsen/logrus v1.9.3 - github.com/spf13/cobra v1.8.0 - golang.org/x/crypto v0.25.0 + github.com/sirupsen/logrus v1.9.4 + github.com/spf13/cobra v1.10.2 + golang.org/x/crypto v0.47.0 gopkg.in/yaml.v2 v2.4.0 ) exclude github.com/docker/docker v24.0.6+incompatible // indirect require ( - github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/containerd/containerd v1.7.13 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/containerd/stargz-snapshotter/estargz v0.15.1 // indirect - github.com/distribution/reference v0.5.0 // indirect - github.com/docker/cli v25.0.3+incompatible // indirect - github.com/docker/distribution v2.8.3+incompatible // indirect - github.com/docker/docker v27.1.1+incompatible // indirect - github.com/docker/docker-credential-helpers v0.8.1 // indirect - github.com/docker/go-connections v0.5.0 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/cli v29.5.3+incompatible // indirect + github.com/docker/docker-credential-helpers v0.9.3 // indirect + github.com/docker/go-connections v0.7.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/gogo/protobuf v1.3.2 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/klauspost/compress v1.17.7 // indirect - github.com/kr/pretty v0.2.1 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/klauspost/compress v1.19.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect - github.com/moby/patternmatcher v0.6.0 // indirect - github.com/moby/sys/user v0.1.0 // indirect - github.com/moby/term v0.5.0 // indirect - github.com/morikuni/aec v1.0.0 // indirect + github.com/moby/go-archive v0.2.0 // indirect + github.com/moby/moby/api v1.55.0 // indirect + github.com/moby/moby/client v0.5.0 // indirect + github.com/moby/patternmatcher v0.6.1 // indirect + github.com/moby/sys/user v0.4.1 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/vbatts/tar-split v0.11.5 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect - go.opentelemetry.io/otel v1.24.0 // indirect - go.opentelemetry.io/otel/metric v1.24.0 // indirect - go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/term v0.22.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect + go.opentelemetry.io/otel v1.41.0 // indirect + go.opentelemetry.io/otel/metric v1.41.0 // indirect + go.opentelemetry.io/otel/trace v1.41.0 // indirect + golang.org/x/sync v0.21.0 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/term v0.43.0 // indirect ) diff --git a/go.sum b/go.sum index 28e419cd..4ae89898 100644 --- a/go.sum +++ b/go.sum @@ -1,181 +1,127 @@ -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/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -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/containerd/containerd v1.7.13 h1:wPYKIeGMN8vaggSKuV1X0wZulpMz4CrgEsZdaCyB6Is= -github.com/containerd/containerd v1.7.13/go.mod h1:zT3up6yTRfEUa6+GsITYIJNgSVL9NQ4x4h1RPzk0Wu4= +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/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/stargz-snapshotter/estargz v0.15.1 h1:eXJjw9RbkLFgioVaTG+G/ZW/0kEe2oEKCdS/ZxIyoCU= -github.com/containerd/stargz-snapshotter/estargz v0.15.1/go.mod h1:gr2RNwukQ/S9Nv33Lt6UC7xEx58C+LHRdoqbEKjz1Kk= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v25.0.3+incompatible h1:KLeNs7zws74oFuVhgZQ5ONGZiXUUdgsdy6/EsX/6284= -github.com/docker/cli v25.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= -github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= -github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.8.1 h1:j/eKUktUltBtMzKqmfLB0PAgqYyMHOp5vfsD1807oKo= -github.com/docker/docker-credential-helpers v0.8.1/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/cli v29.5.3+incompatible h1:nbEFfz774vBwQ5KRYv7c/AghjReqnGISvrRhzjV0evs= +github.com/docker/cli v29.5.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= +github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= +github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c= +github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fsouza/go-dockerclient v1.11.2 h1:Wos4OMUwIjOW2rt8Z10TZSJHxgQH0KcYyf3O86dqFII= -github.com/fsouza/go-dockerclient v1.11.2/go.mod h1:HZN6ky2Mg5mfZO/WZBFDe6XCricqTnDJntfXHZTYnQQ= +github.com/fsouza/go-dockerclient v1.13.2 h1:u+jAOuR9TZ3PAx2pdHA+ALt1ZZhS8Qx+A4d964IqXtw= +github.com/fsouza/go-dockerclient v1.13.2/go.mod h1:SJu2b0vfcF8sbsWs9VVG03iEjsogvo7JADvsi2wVYAI= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -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/go-containerregistry v0.20.1 h1:eTgx9QNYugV4DN5mz4U8hiAGTi1ybXn0TPi4Smd8du0= -github.com/google/go-containerregistry v0.20.1/go.mod h1:YCMFNQeeXeLF+dnhhWkqDItx/JSkH01j1Kis4PsjzFI= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-containerregistry v0.21.7 h1:/vPFuVXDjtFREsVArW+0h1CIl5urnOhzei4X2DMW9IU= +github.com/google/go-containerregistry v0.21.7/go.mod h1:kjSbt7/zMsKLWfnHrIvKvhXHUw91jbe9DNjPPJ32gXE= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= -github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/klauspost/compress v1.19.0 h1:sXLILfc9jV2QYWkzFOPWStmcUVH2RHEB1JCdY2oVvCQ= +github.com/klauspost/compress v1.19.0/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= -github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= -github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= -github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= -github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= -github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8= +github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU= +github.com/moby/moby/api v1.55.0 h1:2/sexvQyqIWS8pRSCFddBfpW2qE7vR7FCL+vN8pxwMc= +github.com/moby/moby/api v1.55.0/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= +github.com/moby/moby/client v0.5.0 h1:5XhyPk2fuOWf6RlSFa3MkIIgDZkF25xToXW8Q/BH7cc= +github.com/moby/moby/client v0.5.0/go.mod h1:rcVpF8ncl9vo5gaIBdol6CnbEtSj1uxMvEV/UrykF/s= +github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= +github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.7.0 h1:ASQNGNROJSuOO6LL6bPHbKvuZu6NU8P4ldPWk31zj/8= +github.com/moby/sys/sequential v0.7.0/go.mod h1:NfSTAp6V3fw4tmkD62PEcOKeZKquXT8VKCkf7aVR79o= +github.com/moby/sys/user v0.4.1 h1:RgjRlaDKi/Xmyrz4t8lyzXT6v2ooFeO/7xtchmhVWE0= +github.com/moby/sys/user v0.4.1/go.mod h1:E9QsW5WRe1kUAf7kW8hXKwu1uhsZEAdPLYHYSDudF4Y= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts= -github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= -go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= -go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= -go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= -go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= -go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o= -go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= -go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= -go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= -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= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -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/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c= +go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE= +go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ= +go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= +go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0= +go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -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/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -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/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -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/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= -google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= -gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/internal/pkgutil/BUILD.bazel b/internal/pkgutil/BUILD.bazel new file mode 100644 index 00000000..b63c8b07 --- /dev/null +++ b/internal/pkgutil/BUILD.bazel @@ -0,0 +1,43 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "pkgutil", + srcs = [ + "fs_utils.go", + "image_utils.go", + "root_path.go", + "tar_utils.go", + "transport_builder.go", + ], + importpath = "github.com/GoogleContainerTools/container-structure-test/internal/pkgutil", + visibility = ["//:__subpackages__"], + deps = [ + "@com_github_google_go_containerregistry//pkg/authn", + "@com_github_google_go_containerregistry//pkg/name", + "@com_github_google_go_containerregistry//pkg/v1:pkg", + "@com_github_google_go_containerregistry//pkg/v1/daemon", + "@com_github_google_go_containerregistry//pkg/v1/layout", + "@com_github_google_go_containerregistry//pkg/v1/mutate", + "@com_github_google_go_containerregistry//pkg/v1/remote", + "@com_github_google_go_containerregistry//pkg/v1/tarball", + "@com_github_moby_sys_sequential//:sequential", + "@com_github_pkg_errors//:errors", + "@com_github_sirupsen_logrus//:logrus", + ], +) + +go_test( + name = "pkgutil_test", + srcs = [ + "image_utils_test.go", + "tar_utils_test.go", + ], + embed = [":pkgutil"], + deps = [ + "@com_github_google_go_containerregistry//pkg/v1:pkg", + "@com_github_google_go_containerregistry//pkg/v1/empty", + "@com_github_google_go_containerregistry//pkg/v1/layout", + "@com_github_google_go_containerregistry//pkg/v1/mutate", + "@com_github_google_go_containerregistry//pkg/v1/tarball", + ], +) diff --git a/internal/pkgutil/image_utils.go b/internal/pkgutil/image_utils.go index 24c38a35..05f94b74 100644 --- a/internal/pkgutil/image_utils.go +++ b/internal/pkgutil/image_utils.go @@ -18,6 +18,7 @@ package pkgutil import ( "archive/tar" + stderrors "errors" "fmt" "io" "io/ioutil" @@ -32,6 +33,7 @@ import ( "github.com/google/go-containerregistry/pkg/name" "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/daemon" + "github.com/google/go-containerregistry/pkg/v1/layout" "github.com/google/go-containerregistry/pkg/v1/mutate" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/tarball" @@ -180,6 +182,119 @@ func GetImage(imageName string, includeLayers bool, cacheDir string) (Image, err }, nil } +// ImageFromV1 takes a pre-loaded v1.Image and extracts its filesystem, +// returning an Image suitable for use with the tar driver. +func ImageFromV1(img v1.Image, source string) (Image, error) { + imageDigest, err := getImageDigest(img) + if err != nil { + return Image{}, err + } + path, err := getExtractPathForName("", "") + if err != nil { + return Image{}, err + } + if err := GetFileSystemForImage(img, path, nil); err != nil { + os.RemoveAll(path) + return Image{}, errors.Wrap(err, "getting filesystem for image") + } + return Image{ + Image: img, + Source: source, + FSPath: path, + Digest: imageDigest, + }, nil +} + +// maxSubIndexes caps how deep findImageInIndex recurses into nested image +// indexes, guarding against malicious or malformed layouts. +const maxSubIndexes = 5 + +// errNoImageFound reports that no descriptor in an index satisfied the +// requested platform. It is used as a sentinel so recursion can distinguish +// "this subtree had no match" from a real error. +var errNoImageFound = stderrors.New("no compatible image found") + +// ImageFromOCILayout loads a single image from an OCI image layout directory. +// It returns the image and its descriptor (for access to annotations). The +// layout must contain exactly one entry. When that entry is a multi-arch image +// index, the descriptor matching platform (e.g. "linux/amd64") is selected. +func ImageFromOCILayout(path, platform string) (v1.Image, v1.Descriptor, error) { + l, err := layout.ImageIndexFromPath(path) + if err != nil { + return nil, v1.Descriptor{}, errors.Wrapf(err, "loading %s as OCI layout", path) + } + m, err := l.IndexManifest() + if err != nil { + return nil, v1.Descriptor{}, errors.Wrapf(err, "reading OCI index manifest %s", path) + } + if len(m.Manifests) != 1 { + return nil, v1.Descriptor{}, errors.Errorf("OCI layout contains %d entries, expected only one", len(m.Manifests)) + } + + desc := m.Manifests[0] + if desc.MediaType.IsIndex() { + requirements, err := v1.ParsePlatform(platform) + if err != nil { + return nil, v1.Descriptor{}, errors.Wrapf(err, "parsing platform %q", platform) + } + childIndex, err := l.ImageIndex(desc.Digest) + if err != nil { + return nil, v1.Descriptor{}, errors.Wrapf(err, "reading image index from %s", path) + } + img, err := findImageInIndex(childIndex, requirements, 0) + if err != nil { + return nil, v1.Descriptor{}, errors.Wrapf(err, "selecting image for platform %q from %s", platform, path) + } + return img, desc, nil + } + + img, err := l.Image(desc.Digest) + if err != nil { + return nil, v1.Descriptor{}, errors.Wrapf(err, "getting image from %s", path) + } + + return img, desc, nil +} + +// findImageInIndex walks an image index, recursing into nested sub-indexes up +// to maxSubIndexes deep, and returns the first image whose platform satisfies +// requirements. Descriptors without a platform field (which OCI permits) are +// skipped rather than dereferenced, since Descriptor.Platform is a nil-able +// pointer and Satisfies has a value receiver. +func findImageInIndex(index v1.ImageIndex, requirements *v1.Platform, depth int) (v1.Image, error) { + if depth > maxSubIndexes { + return nil, errors.Errorf("too many sub-indexes (>%d)", maxSubIndexes) + } + + manifest, err := index.IndexManifest() + if err != nil { + return nil, errors.Wrap(err, "reading index manifest") + } + + for _, desc := range manifest.Manifests { + if desc.MediaType.IsImage() { + if desc.Platform == nil { + continue + } + if desc.Platform.Satisfies(*requirements) { + return index.Image(desc.Digest) + } + } + if desc.MediaType.IsIndex() { + childIndex, err := index.ImageIndex(desc.Digest) + if err != nil { + return nil, err + } + img, err := findImageInIndex(childIndex, requirements, depth+1) + if !stderrors.Is(err, errNoImageFound) { + return img, err + } + } + } + + return nil, errNoImageFound +} + func getExtractPathForName(name string, cacheDir string) (string, error) { path := cacheDir var err error diff --git a/internal/pkgutil/image_utils_test.go b/internal/pkgutil/image_utils_test.go new file mode 100644 index 00000000..6ac5dd10 --- /dev/null +++ b/internal/pkgutil/image_utils_test.go @@ -0,0 +1,229 @@ +package pkgutil + +import ( + "archive/tar" + "bytes" + "os" + "path/filepath" + "testing" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/empty" + "github.com/google/go-containerregistry/pkg/v1/layout" + "github.com/google/go-containerregistry/pkg/v1/mutate" + "github.com/google/go-containerregistry/pkg/v1/tarball" +) + +// testImageWithFile builds a single-layer image whose filesystem contains the +// named file with the given contents. +func testImageWithFile(t *testing.T, name, body string) v1.Image { + t.Helper() + + var buf bytes.Buffer + tw := tar.NewWriter(&buf) + if err := tw.WriteHeader(&tar.Header{ + Name: name, + Mode: 0644, + Size: int64(len(body)), + }); err != nil { + t.Fatal(err) + } + if _, err := tw.Write([]byte(body)); err != nil { + t.Fatal(err) + } + if err := tw.Close(); err != nil { + t.Fatal(err) + } + + layer, err := tarball.LayerFromReader(bytes.NewReader(buf.Bytes())) + if err != nil { + t.Fatal(err) + } + img, err := mutate.AppendLayers(empty.Image, layer) + if err != nil { + t.Fatal(err) + } + return img +} + +// writeOCILayout writes the given images into a fresh OCI layout directory and +// returns its path. +func writeOCILayout(t *testing.T, imgs ...v1.Image) string { + t.Helper() + + dir := t.TempDir() + l, err := layout.Write(dir, empty.Index) + if err != nil { + t.Fatal(err) + } + for _, img := range imgs { + if err := l.AppendImage(img); err != nil { + t.Fatal(err) + } + } + return dir +} + +// platformedImage pairs an image with the platform descriptor it should be +// indexed under. A nil platform produces an index entry whose Descriptor.Platform +// is nil, which OCI permits and which must not panic Satisfies. +type platformedImage struct { + img v1.Image + platform *v1.Platform +} + +// writeMultiArchOCILayout writes a single multi-arch image index (built from the +// given images and platforms) as the sole entry of a fresh OCI layout directory. +// This mirrors the shape produced by `crane pull --format=oci` of a manifest list. +func writeMultiArchOCILayout(t *testing.T, entries ...platformedImage) string { + t.Helper() + + idx := v1.ImageIndex(empty.Index) + for _, e := range entries { + add := mutate.IndexAddendum{Add: e.img} + if e.platform != nil { + add.Descriptor = v1.Descriptor{Platform: e.platform} + } + idx = mutate.AppendManifests(idx, add) + } + + dir := t.TempDir() + l, err := layout.Write(dir, empty.Index) + if err != nil { + t.Fatal(err) + } + if err := l.AppendIndex(idx); err != nil { + t.Fatal(err) + } + return dir +} + +func TestImageFromOCILayoutSelectsMatchingPlatform(t *testing.T) { + amd64 := testImageWithFile(t, "etc/amd64.txt", "amd64-data") + arm64 := testImageWithFile(t, "etc/arm64.txt", "arm64-data") + dir := writeMultiArchOCILayout(t, + platformedImage{img: amd64, platform: &v1.Platform{OS: "linux", Architecture: "amd64"}}, + platformedImage{img: arm64, platform: &v1.Platform{OS: "linux", Architecture: "arm64"}}, + ) + + gotImg, _, err := ImageFromOCILayout(dir, "linux/arm64") + if err != nil { + t.Fatal(err) + } + + wantDigest, err := arm64.Digest() + if err != nil { + t.Fatal(err) + } + gotDigest, err := gotImg.Digest() + if err != nil { + t.Fatal(err) + } + if gotDigest != wantDigest { + t.Fatalf("selected image digest = %v, want arm64 image %v", gotDigest, wantDigest) + } +} + +// TestImageFromOCILayoutSkipsNilPlatform guards the nil-deref fix: an index +// descriptor without a platform field must be skipped rather than passed to the +// value-receiver Satisfies, which would panic on a nil *v1.Platform. +func TestImageFromOCILayoutSkipsNilPlatform(t *testing.T) { + noPlatform := testImageWithFile(t, "etc/nop.txt", "no-platform-data") + amd64 := testImageWithFile(t, "etc/amd64.txt", "amd64-data") + dir := writeMultiArchOCILayout(t, + platformedImage{img: noPlatform, platform: nil}, + platformedImage{img: amd64, platform: &v1.Platform{OS: "linux", Architecture: "amd64"}}, + ) + + gotImg, _, err := ImageFromOCILayout(dir, "linux/amd64") + if err != nil { + t.Fatal(err) + } + + wantDigest, err := amd64.Digest() + if err != nil { + t.Fatal(err) + } + gotDigest, err := gotImg.Digest() + if err != nil { + t.Fatal(err) + } + if gotDigest != wantDigest { + t.Fatalf("selected image digest = %v, want amd64 image %v", gotDigest, wantDigest) + } +} + +func TestImageFromV1ExtractsFilesystem(t *testing.T) { + img := testImageWithFile(t, "etc/known.txt", "image-data") + + result, err := ImageFromV1(img, "test-source") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(result.FSPath) + + if result.Source != "test-source" { + t.Fatalf("Source = %q, want %q", result.Source, "test-source") + } + if result.FSPath == "" { + t.Fatal("FSPath is empty") + } + wantDigest, err := img.Digest() + if err != nil { + t.Fatal(err) + } + if result.Digest != wantDigest { + t.Fatalf("Digest = %v, want %v", result.Digest, wantDigest) + } + + got, err := os.ReadFile(filepath.Join(result.FSPath, "etc", "known.txt")) + if err != nil { + t.Fatal(err) + } + if string(got) != "image-data" { + t.Fatalf("extracted file = %q, want %q", got, "image-data") + } +} + +func TestImageFromOCILayoutLoadsSingleImage(t *testing.T) { + img := testImageWithFile(t, "etc/known.txt", "image-data") + dir := writeOCILayout(t, img) + + gotImg, _, err := ImageFromOCILayout(dir, "linux/amd64") + if err != nil { + t.Fatal(err) + } + + wantDigest, err := img.Digest() + if err != nil { + t.Fatal(err) + } + gotDigest, err := gotImg.Digest() + if err != nil { + t.Fatal(err) + } + if gotDigest != wantDigest { + t.Fatalf("loaded image digest = %v, want %v", gotDigest, wantDigest) + } +} + +func TestImageFromOCILayoutMissingPath(t *testing.T) { + missing := filepath.Join(t.TempDir(), "does-not-exist") + + _, _, err := ImageFromOCILayout(missing, "linux/amd64") + if err == nil { + t.Fatal("ImageFromOCILayout accepted a missing layout path") + } +} + +func TestImageFromOCILayoutRejectsMultipleEntries(t *testing.T) { + dir := writeOCILayout(t, + testImageWithFile(t, "etc/a.txt", "a-data"), + testImageWithFile(t, "etc/b.txt", "b-data"), + ) + + _, _, err := ImageFromOCILayout(dir, "linux/amd64") + if err == nil { + t.Fatal("ImageFromOCILayout accepted a layout with multiple entries") + } +} diff --git a/internal/pkgutil/root_path.go b/internal/pkgutil/root_path.go new file mode 100644 index 00000000..6b917c31 --- /dev/null +++ b/internal/pkgutil/root_path.go @@ -0,0 +1,92 @@ +package pkgutil + +import ( + "fmt" + "os" + pathpkg "path" + "path/filepath" + "strings" +) + +const maxRootPathSymlinks = 255 + +// ResolvePathInRoot resolves imagePath below root. Absolute paths are treated as +// absolute within root, and symlink targets are resolved using the same image +// root instead of the host root. +func ResolvePathInRoot(root, imagePath string, followFinalSymlink bool) (string, error) { + absRoot, err := filepath.Abs(root) + if err != nil { + return "", err + } + absRoot = filepath.Clean(absRoot) + + parts, err := resolveRootPathParts(nil, imagePath) + if err != nil { + return "", err + } + + resolved := []string{} + symlinkCount := 0 + for i := 0; i < len(parts); i++ { + part := parts[i] + candidate := joinRootPath(absRoot, append(resolved, part)) + isFinal := i == len(parts)-1 + if !isFinal || followFinalSymlink { + info, err := os.Lstat(candidate) + if err != nil && !os.IsNotExist(err) { + return "", err + } + if err == nil && info.Mode()&os.ModeSymlink != 0 { + symlinkCount++ + if symlinkCount > maxRootPathSymlinks { + return "", fmt.Errorf("too many symlinks resolving path %q", imagePath) + } + linkTarget, err := os.Readlink(candidate) + if err != nil { + return "", err + } + base := resolved + if pathpkg.IsAbs(filepath.ToSlash(linkTarget)) { + base = nil + } + linkParts, err := resolveRootPathParts(base, linkTarget) + if err != nil { + return "", err + } + parts = append(linkParts, parts[i+1:]...) + resolved = nil + i = -1 + continue + } + } + resolved = append(resolved, part) + } + return joinRootPath(absRoot, resolved), nil +} + +func resolveRootPathParts(base []string, imagePath string) ([]string, error) { + cleaned := filepath.ToSlash(imagePath) + if pathpkg.IsAbs(cleaned) { + base = nil + } + + parts := append([]string{}, base...) + for _, part := range strings.Split(pathpkg.Clean(cleaned), "/") { + switch part { + case "", ".": + continue + case "..": + if len(parts) == 0 { + return nil, fmt.Errorf("path %q escapes root", imagePath) + } + parts = parts[:len(parts)-1] + default: + parts = append(parts, part) + } + } + return parts, nil +} + +func joinRootPath(root string, parts []string) string { + return filepath.Join(append([]string{root}, parts...)...) +} diff --git a/internal/pkgutil/tar_utils.go b/internal/pkgutil/tar_utils.go index 9d0075cb..f79d1e81 100644 --- a/internal/pkgutil/tar_utils.go +++ b/internal/pkgutil/tar_utils.go @@ -21,6 +21,7 @@ import ( "fmt" "io" "os" + pathpkg "path" "path/filepath" "strings" @@ -50,7 +51,10 @@ func unpackTar(tr *tar.Reader, path string, whitelist []string) error { if err != nil { return errors.Wrap(err, "Error getting next tar header") } - target := filepath.Clean(filepath.Join(path, header.Name)) + target, err := ResolvePathInRoot(path, header.Name, false) + if err != nil { + return errors.Wrap(err, "resolving tar entry path") + } // Make sure the target isn't part of the whitelist if checkWhitelist(target, whitelist) { continue @@ -60,16 +64,18 @@ func unpackTar(tr *tar.Reader, path string, whitelist []string) error { // if its a dir and it doesn't exist create it case tar.TypeDir: - if _, err := os.Stat(target); os.IsNotExist(err) { - if mode.Perm()&(1<<(uint(7))) == 0 { - logrus.Debugf("Write permission bit not set on %s by default; setting manually", target) - originalMode := mode - mode = mode | (1 << uint(7)) - // keep track of original file permission to reset later + if _, err := os.Lstat(target); os.IsNotExist(err) { + // cst inspects the extracted tree as the (often non-root) test user. + // A directory missing any owner rwx bit can't be populated during + // extraction or traversed during assertions, so force owner rwx while + // extracting and record the original mode to restore afterwards. + if mode.Perm()&0700 != 0700 { + logrus.Debugf("Owner rwx not fully set on %s by default; forcing it for extraction", target) originalPerms = append(originalPerms, OriginalPerm{ path: target, - perm: originalMode, + perm: mode, }) + mode |= 0700 } logrus.Debugf("Creating directory %s with permissions %v", target, mode) if err := os.MkdirAll(target, mode); err != nil { @@ -93,7 +99,7 @@ func unpackTar(tr *tar.Reader, path string, whitelist []string) error { } // It's possible we end up creating files that can't be overwritten based on their permissions. // Explicitly delete an existing file before continuing. - if _, err := os.Stat(target); !os.IsNotExist(err) { + if _, err := os.Lstat(target); !os.IsNotExist(err) { logrus.Debugf("Removing %s for overwrite", target) if err := os.Remove(target); err != nil { logrus.Errorf("error removing file %s", target) @@ -109,8 +115,18 @@ func unpackTar(tr *tar.Reader, path string, whitelist []string) error { } // manually set permissions on file, since the default umask (022) will interfere if err = os.Chmod(target, mode); err != nil { - logrus.Errorf("Error updating file permissions on %s", target) - return err + // Non-root users on some platforms (notably macOS/BSD) cannot set the + // setuid/setgid/sticky bits via chmod. cst inspects the extracted tree + // as that user, so retry without those bits rather than aborting + // extraction. Where the OS permits them (e.g. Linux) the first chmod + // succeeds and the bits are preserved. + if base := mode &^ (os.ModeSetuid | os.ModeSetgid | os.ModeSticky); base != mode { + err = os.Chmod(target, base) + } + if err != nil { + logrus.Errorf("Error updating file permissions on %s", target) + return err + } } _, err = io.Copy(currFile, tr) if err != nil { @@ -118,9 +134,12 @@ func unpackTar(tr *tar.Reader, path string, whitelist []string) error { } currFile.Close() case tar.TypeSymlink: + if _, err := ResolvePathInRoot(path, tarLinkTarget(header.Name, header.Linkname), true); err != nil { + return errors.Wrap(err, "resolving tar symlink path") + } // It's possible we end up creating files that can't be overwritten based on their permissions. // Explicitly delete an existing file before continuing. - if _, err := os.Stat(target); !os.IsNotExist(err) { + if _, err := os.Lstat(target); !os.IsNotExist(err) { logrus.Debugf("Removing %s to create symlink", target) if err := os.RemoveAll(target); err != nil { logrus.Debugf("Unable to remove %s: %s", target, err) @@ -131,11 +150,16 @@ func unpackTar(tr *tar.Reader, path string, whitelist []string) error { logrus.Errorf("Failed to create symlink between %s and %s: %s", header.Linkname, target, err) } case tar.TypeLink: - linkname := filepath.Clean(filepath.Join(path, header.Linkname)) + linkname, err := ResolvePathInRoot(path, header.Linkname, false) + if err != nil { + return errors.Wrap(err, "resolving tar hard link path") + } // Check if the linkname already exists if _, err := os.Stat(linkname); !os.IsNotExist(err) { // If it exists, create the hard link - resolveHardlink(linkname, target) + if err := resolveHardlink(path, linkname, target); err != nil { + return errors.Wrap(err, fmt.Sprintf("Unable to create hard link from %s to %s", linkname, target)) + } } else { hardlinks.Store(target, linkname) } @@ -148,7 +172,7 @@ func unpackTar(tr *tar.Reader, path string, whitelist []string) error { logrus.Info("Resolving hard links") if _, err := os.Stat(linkname); !os.IsNotExist(err) { // If it exists, create the hard link - if err := resolveHardlink(linkname, target); err != nil { + if err := resolveHardlink(path, linkname, target); err != nil { resolveError.Store(errors.Wrap(err, fmt.Sprintf("Unable to create hard link from %s to %s", linkname, target))) return false } @@ -159,16 +183,47 @@ func unpackTar(tr *tar.Reader, path string, whitelist []string) error { return resolveError.Load().(error) } - // reset all original file + // Restore each directory's original mode, but keep the owner-execute bit so cst + // can still traverse it to inspect its contents. A directory shipped without + // owner-execute is therefore reported with that bit set. for _, perm := range originalPerms { - if err := os.Chmod(perm.path, perm.perm); err != nil { + if err := os.Chmod(perm.path, perm.perm|0100); err != nil { return err } } return nil } -func resolveHardlink(linkname, target string) error { +func tarLinkTarget(name, linkname string) string { + if pathpkg.IsAbs(filepath.ToSlash(linkname)) { + return linkname + } + return pathpkg.Join(pathpkg.Dir(filepath.ToSlash(name)), filepath.ToSlash(linkname)) +} + +func resolveHardlink(root, linkname, target string) error { + info, err := os.Lstat(linkname) + if err != nil { + return err + } + if info.Mode()&os.ModeSymlink != 0 { + linkTarget, err := os.Readlink(linkname) + if err != nil { + return err + } + relLink, err := filepath.Rel(root, linkname) + if err != nil { + return err + } + if _, err := ResolvePathInRoot(root, tarLinkTarget(relLink, linkTarget), true); err != nil { + return err + } + if err := os.Symlink(linkTarget, target); err != nil { + return err + } + logrus.Debugf("Created symlink from %s to %s for hard link source %s", linkTarget, target, linkname) + return nil + } if err := os.Link(linkname, target); err != nil { return err } diff --git a/internal/pkgutil/tar_utils_test.go b/internal/pkgutil/tar_utils_test.go new file mode 100644 index 00000000..5c6cf36c --- /dev/null +++ b/internal/pkgutil/tar_utils_test.go @@ -0,0 +1,300 @@ +package pkgutil + +import ( + "archive/tar" + "bytes" + "os" + "path/filepath" + "testing" +) + +func testTarReader(t *testing.T, entries ...func(*tar.Writer) error) *tar.Reader { + t.Helper() + + var buf bytes.Buffer + tw := tar.NewWriter(&buf) + for _, entry := range entries { + if err := entry(tw); err != nil { + t.Fatal(err) + } + } + if err := tw.Close(); err != nil { + t.Fatal(err) + } + return tar.NewReader(bytes.NewReader(buf.Bytes())) +} + +func testTarFile(name, body string) func(*tar.Writer) error { + return func(tw *tar.Writer) error { + if err := tw.WriteHeader(&tar.Header{ + Name: name, + Mode: 0644, + Size: int64(len(body)), + }); err != nil { + return err + } + _, err := tw.Write([]byte(body)) + return err + } +} + +func testTarDir(name string) func(*tar.Writer) error { + return func(tw *tar.Writer) error { + return tw.WriteHeader(&tar.Header{ + Name: name, + Mode: 0755, + Typeflag: tar.TypeDir, + }) + } +} + +func testTarDirMode(name string, mode int64) func(*tar.Writer) error { + return func(tw *tar.Writer) error { + return tw.WriteHeader(&tar.Header{ + Name: name, + Mode: mode, + Typeflag: tar.TypeDir, + }) + } +} + +func testTarSymlink(name, linkname string) func(*tar.Writer) error { + return func(tw *tar.Writer) error { + return tw.WriteHeader(&tar.Header{ + Name: name, + Linkname: linkname, + Typeflag: tar.TypeSymlink, + }) + } +} + +func testTarHardlink(name, linkname string) func(*tar.Writer) error { + return func(tw *tar.Writer) error { + return tw.WriteHeader(&tar.Header{ + Name: name, + Linkname: linkname, + Typeflag: tar.TypeLink, + }) + } +} + +func TestUnpackTarRejectsEntriesOutsideRoot(t *testing.T) { + parent := t.TempDir() + root := filepath.Join(parent, "root") + if err := os.MkdirAll(root, 0755); err != nil { + t.Fatal(err) + } + + err := unpackTar(testTarReader(t, testTarFile("../outside.txt", "outside-data")), root, nil) + if err == nil { + t.Fatal("unpackTar allowed an entry outside the root") + } + if _, err := os.Stat(filepath.Join(parent, "outside.txt")); !os.IsNotExist(err) { + t.Fatalf("outside path exists after rejected unpack: %v", err) + } +} + +func TestUnpackTarRejectsSymlinksOutsideRoot(t *testing.T) { + parent := t.TempDir() + root := filepath.Join(parent, "root") + if err := os.MkdirAll(root, 0755); err != nil { + t.Fatal(err) + } + + err := unpackTar(testTarReader(t, testTarSymlink("link", "..")), root, nil) + if err == nil { + t.Fatal("unpackTar allowed a symlink outside the root") + } +} + +func TestUnpackTarRejectsHardlinksOutsideRoot(t *testing.T) { + root := t.TempDir() + + err := unpackTar(testTarReader(t, testTarHardlink("link", "../outside.txt")), root, nil) + if err == nil { + t.Fatal("unpackTar allowed a hard link outside the root") + } +} + +func TestUnpackTarHardlinkToSymlinkDoesNotFollowTarget(t *testing.T) { + root := t.TempDir() + + err := unpackTar(testTarReader(t, + testTarFile("file.txt", "image-data"), + testTarSymlink("sym", "file.txt"), + testTarHardlink("hard", "sym"), + ), root, nil) + if err != nil { + t.Fatal(err) + } + + info, err := os.Lstat(filepath.Join(root, "hard")) + if err != nil { + t.Fatal(err) + } + if info.Mode()&os.ModeSymlink == 0 { + t.Fatalf("hard link to symlink should remain a symlink, got mode %s", info.Mode()) + } + target, err := os.Readlink(filepath.Join(root, "hard")) + if err != nil { + t.Fatal(err) + } + if target != "file.txt" { + t.Fatalf("hard link to symlink target = %q", target) + } +} + +func TestUnpackTarRejectsHardlinkSourceSymlinkOutsideRoot(t *testing.T) { + parent := t.TempDir() + root := filepath.Join(parent, "root") + if err := os.MkdirAll(root, 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(parent, "outside.txt"), []byte("outside-data"), 0644); err != nil { + t.Fatal(err) + } + if err := os.Symlink("../outside.txt", filepath.Join(root, "sym")); err != nil { + t.Fatal(err) + } + + err := unpackTar(testTarReader(t, testTarHardlink("hard", "sym")), root, nil) + if err == nil { + t.Fatal("unpackTar allowed a hard link through a symlink outside the root") + } + if _, err := os.Lstat(filepath.Join(root, "hard")); !os.IsNotExist(err) { + t.Fatalf("hard link path exists after rejected unpack: %v", err) + } +} + +func TestUnpackTarReadsFileUnderNonTraversableDir(t *testing.T) { + if os.Geteuid() == 0 { + t.Skip("root bypasses directory traversal permissions; bug only manifests for the non-root test user") + } + root := t.TempDir() + + // A directory shipped without the owner execute bit (e.g. /licenses at 0644) + // can neither be populated during extraction nor traversed to inspect the + // files inside it when running as the non-root test user. + err := unpackTar(testTarReader(t, + testTarDirMode("licenses", 0644), + testTarFile("licenses/copyright", "license-text"), + ), root, nil) + if err != nil { + t.Fatalf("unpackTar failed on a file under a non-traversable dir: %v", err) + } + + got, err := os.ReadFile(filepath.Join(root, "licenses", "copyright")) + if err != nil { + t.Fatalf("reading file under non-traversable dir: %v", err) + } + if string(got) != "license-text" { + t.Fatalf("file content = %q, want %q", got, "license-text") + } +} + +func TestUnpackTarForcesOwnerExecuteOnNonTraversableDir(t *testing.T) { + if os.Geteuid() == 0 { + t.Skip("root bypasses directory traversal permissions; bug only manifests for the non-root test user") + } + root := t.TempDir() + + if err := unpackTar(testTarReader(t, + testTarDirMode("licenses", 0644), + testTarFile("licenses/copyright", "license-text"), + ), root, nil); err != nil { + t.Fatal(err) + } + + // Owner-execute is forced on so cst can traverse the dir to inspect its + // contents; the remaining shipped bits are preserved (0644 -> 0744). + info, err := os.Lstat(filepath.Join(root, "licenses")) + if err != nil { + t.Fatal(err) + } + if got := info.Mode().Perm(); got != 0744 { + t.Fatalf("non-traversable dir reported mode = %o, want 0744", got) + } +} + +func TestUnpackTarPreservesModeOfTraversableDir(t *testing.T) { + root := t.TempDir() + // Runs before TempDir's own RemoveAll (cleanups are LIFO): make the write-less + // dir writable again so the non-root test user can unlink its contents. + t.Cleanup(func() { _ = os.Chmod(filepath.Join(root, "app"), 0755) }) + + // A write-less but already-traversable directory (0555) is the common case + // the temp-permission logic exists for; its mode must round-trip exactly. + if err := unpackTar(testTarReader(t, + testTarDirMode("app", 0555), + testTarFile("app/run", "bin"), + ), root, nil); err != nil { + t.Fatal(err) + } + + info, err := os.Lstat(filepath.Join(root, "app")) + if err != nil { + t.Fatal(err) + } + if got := info.Mode().Perm(); got != 0555 { + t.Fatalf("traversable dir reported mode = %o, want 0555", got) + } +} + +func TestUnpackTarExtractsSetuidFile(t *testing.T) { + if os.Geteuid() == 0 { + t.Skip("root can set setuid unconditionally; the bug only manifests for the non-root test user") + } + root := t.TempDir() + + // A setuid regular file (e.g. /usr/bin/chfn) must extract successfully. On + // platforms where a non-root user cannot chmod the setuid bit (macOS/BSD), + // the initial chmod fails and the graceful fallback re-chmods without the + // special bits instead of aborting. On Linux the first chmod succeeds and the + // bit is preserved. Either way, extraction must not error and the file must be + // readable so tests inside such an image can run. + err := unpackTar(testTarReader(t, func(tw *tar.Writer) error { + body := "bin" + if err := tw.WriteHeader(&tar.Header{ + Name: "usr/bin/chfn", + Mode: 04755, // setuid + Size: int64(len(body)), + Typeflag: tar.TypeReg, + }); err != nil { + return err + } + _, err := tw.Write([]byte(body)) + return err + }), root, nil) + if err != nil { + t.Fatalf("unpackTar failed on a setuid file: %v", err) + } + + got, err := os.ReadFile(filepath.Join(root, "usr", "bin", "chfn")) + if err != nil { + t.Fatalf("reading extracted setuid file: %v", err) + } + if string(got) != "bin" { + t.Fatalf("setuid file content = %q, want %q", got, "bin") + } +} + +func TestUnpackTarResolvesSymlinksWithinRoot(t *testing.T) { + root := t.TempDir() + + err := unpackTar(testTarReader(t, + testTarDir("etc"), + testTarSymlink("link", "/etc"), + testTarFile("link/generated.txt", "image-data"), + ), root, nil) + if err != nil { + t.Fatal(err) + } + + got, err := os.ReadFile(filepath.Join(root, "etc", "generated.txt")) + if err != nil { + t.Fatal(err) + } + if string(got) != "image-data" { + t.Fatalf("unpacked file through image symlink = %q", got) + } +} diff --git a/pkg/color/BUILD.bazel b/pkg/color/BUILD.bazel new file mode 100644 index 00000000..68f30e29 --- /dev/null +++ b/pkg/color/BUILD.bazel @@ -0,0 +1,16 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "color", + srcs = ["formatter.go"], + importpath = "github.com/GoogleContainerTools/container-structure-test/pkg/color", + visibility = ["//visibility:public"], + deps = ["@org_golang_x_crypto//ssh/terminal"], +) + +go_test( + name = "color_test", + srcs = ["formatter_test.go"], + embed = [":color"], + deps = ["//testutil"], +) diff --git a/pkg/config/BUILD.bazel b/pkg/config/BUILD.bazel new file mode 100644 index 00000000..0890520f --- /dev/null +++ b/pkg/config/BUILD.bazel @@ -0,0 +1,9 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "config", + srcs = ["options.go"], + importpath = "github.com/GoogleContainerTools/container-structure-test/pkg/config", + visibility = ["//visibility:public"], + deps = ["//pkg/types/unversioned"], +) diff --git a/pkg/config/options.go b/pkg/config/options.go index ae24c3c3..23cc81a6 100644 --- a/pkg/config/options.go +++ b/pkg/config/options.go @@ -26,6 +26,7 @@ type StructureTestOptions struct { Platform string Metadata string TestReport string + TestReportFormat unversioned.OutputValue ConfigFiles []string JSON bool diff --git a/pkg/drivers/BUILD.bazel b/pkg/drivers/BUILD.bazel new file mode 100644 index 00000000..755c4ce6 --- /dev/null +++ b/pkg/drivers/BUILD.bazel @@ -0,0 +1,31 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "drivers", + srcs = [ + "docker_driver.go", + "driver.go", + "host_driver.go", + "tar_driver.go", + ], + importpath = "github.com/GoogleContainerTools/container-structure-test/pkg/drivers", + visibility = ["//visibility:public"], + deps = [ + "//internal/pkgutil", + "//pkg/types/unversioned", + "//pkg/utils", + "@com_github_fsouza_go_dockerclient//:go-dockerclient", + "@com_github_google_go_containerregistry//pkg/v1:pkg", + "@com_github_google_go_containerregistry//pkg/v1/mutate", + "@com_github_joho_godotenv//:godotenv", + "@com_github_pkg_errors//:errors", + "@com_github_sirupsen_logrus//:logrus", + ], +) + +go_test( + name = "drivers_test", + srcs = ["tar_driver_test.go"], + embed = [":drivers"], + deps = ["//internal/pkgutil"], +) diff --git a/pkg/drivers/docker_driver.go b/pkg/drivers/docker_driver.go index 47083f98..86d02d14 100644 --- a/pkg/drivers/docker_driver.go +++ b/pkg/drivers/docker_driver.go @@ -19,13 +19,14 @@ import ( "bufio" "bytes" "fmt" - "github.com/joho/godotenv" "io" "os" "path" "path/filepath" "strings" + "github.com/joho/godotenv" + "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -64,27 +65,28 @@ func NewDockerDriver(args DriverConfig) (Driver, error) { } func (d *DockerDriver) hostConfig() *docker.HostConfig { - if d.runOpts.IsSet() && d.runtime != "" { - return &docker.HostConfig{ - Capabilities: d.runOpts.Capabilities, - Binds: d.runOpts.BindMounts, - Privileged: d.runOpts.Privileged, - Runtime: d.runtime, - } - } + var hc *docker.HostConfig if d.runOpts.IsSet() { - return &docker.HostConfig{ - Capabilities: d.runOpts.Capabilities, - Binds: d.runOpts.BindMounts, - Privileged: d.runOpts.Privileged, + hc = &docker.HostConfig{ + // CapAdd/CapDrop are honored on Docker API >=1.40. The legacy + // HostConfig.Capabilities field is mutually exclusive with them on + // >=1.40 (setting it makes the daemon ignore CapDrop), so it is not + // set here; pre-1.40 daemons (Docker <19.03) are not supported. + CapAdd: d.runOpts.CapAdd, + CapDrop: d.runOpts.CapDrop, + Binds: d.runOpts.BindMounts, + Privileged: d.runOpts.Privileged, + Sysctls: d.runOpts.Sysctls, + NetworkMode: d.runOpts.Network, } } if d.runtime != "" { - return &docker.HostConfig{ - Runtime: d.runtime, + if hc == nil { + hc = &docker.HostConfig{} } + hc.Runtime = d.runtime } - return nil + return hc } func (d *DockerDriver) Destroy() { @@ -465,7 +467,7 @@ func (d *DockerDriver) GetConfig() (unversioned.Config, error) { ports := []string{} for p := range img.Config.ExposedPorts { - ports = append(ports, p.Port()) + ports = append(ports, string(p)) } return unversioned.Config{ diff --git a/pkg/drivers/driver.go b/pkg/drivers/driver.go index a2145696..9f131423 100644 --- a/pkg/drivers/driver.go +++ b/pkg/drivers/driver.go @@ -29,12 +29,13 @@ const ( ) type DriverConfig struct { - Image string // used by Docker/Tar drivers - Save bool // used by Docker/Tar drivers - Metadata string // used by Host driver - Runtime string // used by Docker driver - Platform string // used by Docker driver - RunOpts unversioned.ContainerRunOptions // used by Docker driver + Image string // used by Docker/Tar drivers + Save bool // used by Docker/Tar drivers + Metadata string // used by Host driver + Runtime string // used by Docker driver + Platform string // used by Docker driver + RunOpts unversioned.ContainerRunOptions // used by Docker driver + OCILayout string // used by Tar driver for OCI layout directories } type Driver interface { @@ -81,7 +82,12 @@ func convertSliceToMap(slice []string) map[string]string { res := make(map[string]string) for _, slicePair := range slice { pair := strings.SplitN(slicePair, "=", 2) - res[pair[0]] = pair[1] + key := pair[0] + value := "" + if len(pair) > 1 { + value = pair[1] + } + res[key] = value } return res } diff --git a/pkg/drivers/driver_test.go b/pkg/drivers/driver_test.go new file mode 100644 index 00000000..3a341b8f --- /dev/null +++ b/pkg/drivers/driver_test.go @@ -0,0 +1,67 @@ +package drivers + +import ( + "testing" +) + +func TestConvertSliceToMapWithMissingEquals(t *testing.T) { + tests := []struct { + name string + input []string + expected map[string]string + }{ + { + name: "empty slice", + input: []string{}, + expected: map[string]string{}, + }, + { + name: "normal key=value pairs", + input: []string{"KEY=value", "FOO=bar"}, + expected: map[string]string{ + "KEY": "value", + "FOO": "bar", + }, + }, + { + name: "bare env var without equals", + input: []string{"EMPTY"}, + expected: map[string]string{ + "EMPTY": "", + }, + }, + { + name: "mixed bare and normal pairs", + input: []string{"EMPTY", "PATH=/usr/local/bin", "ANOTHER_EMPTY", "FOO=bar"}, + expected: map[string]string{ + "EMPTY": "", + "PATH": "/usr/local/bin", + "ANOTHER_EMPTY": "", + "FOO": "bar", + }, + }, + { + name: "value with multiple equals signs", + input: []string{"EQUATION=a=b=c"}, + expected: map[string]string{ + "EQUATION": "a=b=c", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := convertSliceToMap(tt.input) + if len(got) != len(tt.expected) { + t.Fatalf("map length: got %d, expected %d", len(got), len(tt.expected)) + } + for k, v := range tt.expected { + if gotVal, ok := got[k]; !ok { + t.Fatalf("missing key %q", k) + } else if gotVal != v { + t.Fatalf("key %q: got %q, expected %q", k, gotVal, v) + } + } + }) + } +} diff --git a/pkg/drivers/host_driver.go b/pkg/drivers/host_driver.go index 70b45f83..b94fa1e8 100644 --- a/pkg/drivers/host_driver.go +++ b/pkg/drivers/host_driver.go @@ -15,18 +15,16 @@ package drivers import ( + "bytes" "encoding/json" "io/fs" "os" "os/exec" - "strings" "syscall" "github.com/pkg/errors" "github.com/sirupsen/logrus" - "bytes" - "github.com/GoogleContainerTools/container-structure-test/pkg/types/unversioned" v1 "github.com/google/go-containerregistry/pkg/v1" ) @@ -87,7 +85,7 @@ func (d *HostDriver) SetEnv(envVars []unversioned.EnvVar) error { func SetEnvVars(envVars []unversioned.EnvVar) []unversioned.EnvVar { var originalVars []unversioned.EnvVar for _, envVar := range envVars { - originalVars = append(originalVars, unversioned.EnvVar{envVar.Key, os.Getenv(envVar.Key), envVar.IsRegex}) + originalVars = append(originalVars, unversioned.EnvVar{Key: envVar.Key, Value: os.Getenv(envVar.Key), IsRegex: envVar.IsRegex}) if err := os.Setenv(envVar.Key, os.ExpandEnv(envVar.Value)); err != nil { logrus.Errorf("Error setting env var: %s", err) } @@ -183,8 +181,8 @@ func (d *HostDriver) GetConfig() (unversioned.Config, error) { ports := []string{} for p := range config.ExposedPorts { - // docker always appends the protocol to the port, so this is safe - ports = append(ports, strings.Split(p, "/")[0]) + // docker always appends the protocol to the port. preserve it for matching. + ports = append(ports, p) } return unversioned.Config{ diff --git a/pkg/drivers/tar_driver.go b/pkg/drivers/tar_driver.go index af0a2021..a855dd3d 100644 --- a/pkg/drivers/tar_driver.go +++ b/pkg/drivers/tar_driver.go @@ -17,8 +17,6 @@ package drivers import ( "io/fs" "os" - "path/filepath" - "strings" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -35,6 +33,16 @@ type TarDriver struct { } func NewTarDriver(args DriverConfig) (Driver, error) { + if args.OCILayout != "" { + image, err := imageFromOCILayout(args.OCILayout, args.Platform) + if err != nil { + return nil, errors.Wrap(err, "processing OCI layout") + } + return &TarDriver{ + Image: image, + Save: args.Save, + }, nil + } if pkgutil.IsTar(args.Image) { // tar provided, so don't provide any prefix. container-diff can figure this out. image, err := pkgutil.GetImageForName(args.Image) @@ -68,6 +76,14 @@ func NewTarDriver(args DriverConfig) (Driver, error) { }, nil } +func imageFromOCILayout(path, platform string) (pkgutil.Image, error) { + img, _, err := pkgutil.ImageFromOCILayout(path, platform) + if err != nil { + return pkgutil.Image{}, err + } + return pkgutil.ImageFromV1(img, path) +} + func (d *TarDriver) Destroy() { if !d.Save { pkgutil.CleanupImage(d.Image) @@ -138,16 +154,28 @@ func (d *TarDriver) ProcessCommand(_ []unversioned.EnvVar, _ []string) (string, return "", "", -1, errors.New("Tar driver is unable to process commands, please use a different driver") } -func (d *TarDriver) StatFile(path string) (os.FileInfo, error) { - return os.Lstat(filepath.Join(d.Image.FSPath, path)) +func (d *TarDriver) StatFile(imagePath string) (os.FileInfo, error) { + path, err := pkgutil.ResolvePathInRoot(d.Image.FSPath, imagePath, false) + if err != nil { + return nil, err + } + return os.Lstat(path) } -func (d *TarDriver) ReadFile(path string) ([]byte, error) { - return os.ReadFile(filepath.Join(d.Image.FSPath, path)) +func (d *TarDriver) ReadFile(imagePath string) ([]byte, error) { + path, err := pkgutil.ResolvePathInRoot(d.Image.FSPath, imagePath, true) + if err != nil { + return nil, err + } + return os.ReadFile(path) } -func (d *TarDriver) ReadDir(path string) ([]os.FileInfo, error) { - entries, err := os.ReadDir(filepath.Join(d.Image.FSPath, path)) +func (d *TarDriver) ReadDir(imagePath string) ([]os.FileInfo, error) { + path, err := pkgutil.ResolvePathInRoot(d.Image.FSPath, imagePath, true) + if err != nil { + return nil, err + } + entries, err := os.ReadDir(path) if err != nil { return nil, err } @@ -178,8 +206,8 @@ func (d *TarDriver) GetConfig() (unversioned.Config, error) { ports := []string{} for p := range config.ExposedPorts { - // docker always appends the protocol to the port, so this is safe - ports = append(ports, strings.Split(p, "/")[0]) + // docker always appends the protocol to the port. preserve it for matching. + ports = append(ports, p) } return unversioned.Config{ diff --git a/pkg/drivers/tar_driver_test.go b/pkg/drivers/tar_driver_test.go new file mode 100644 index 00000000..84e86eb9 --- /dev/null +++ b/pkg/drivers/tar_driver_test.go @@ -0,0 +1,122 @@ +package drivers + +import ( + "os" + "path/filepath" + "testing" + + "github.com/GoogleContainerTools/container-structure-test/internal/pkgutil" +) + +func TestTarDriverFileOperationsStayWithinImageRoot(t *testing.T) { + parent := t.TempDir() + imageRoot := filepath.Join(parent, "image-root") + if err := os.MkdirAll(filepath.Join(imageRoot, "etc"), 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(imageRoot, "etc", "config.txt"), []byte("image-data"), 0644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(parent, "outside.txt"), []byte("outside-data"), 0644); err != nil { + t.Fatal(err) + } + + driver := &TarDriver{Image: pkgutil.Image{FSPath: imageRoot}} + + if got, err := driver.ReadFile("/etc/config.txt"); err != nil || string(got) != "image-data" { + t.Fatalf("ReadFile inside image root = %q, %v", got, err) + } + if _, err := driver.StatFile("/etc/config.txt"); err != nil { + t.Fatalf("StatFile inside image root: %v", err) + } + if _, err := driver.ReadDir("/etc"); err != nil { + t.Fatalf("ReadDir inside image root: %v", err) + } + + for name, op := range map[string]func() error{ + "ReadFile": func() error { + _, err := driver.ReadFile("../outside.txt") + return err + }, + "StatFile": func() error { + _, err := driver.StatFile("../outside.txt") + return err + }, + "ReadDir": func() error { + _, err := driver.ReadDir("..") + return err + }, + } { + if err := op(); err == nil { + t.Fatalf("%s allowed a path outside the image root", name) + } + } +} + +func TestTarDriverResolvesSymlinksWithinImageRoot(t *testing.T) { + imageRoot := t.TempDir() + if err := os.MkdirAll(filepath.Join(imageRoot, "etc"), 0755); err != nil { + t.Fatal(err) + } + if err := os.MkdirAll(filepath.Join(imageRoot, "bin"), 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(imageRoot, "etc", "config.txt"), []byte("image-data"), 0644); err != nil { + t.Fatal(err) + } + if err := os.Symlink("/etc/config.txt", filepath.Join(imageRoot, "absolute-link")); err != nil { + t.Fatal(err) + } + if err := os.Symlink("../etc/config.txt", filepath.Join(imageRoot, "bin", "relative-link")); err != nil { + t.Fatal(err) + } + + driver := &TarDriver{Image: pkgutil.Image{FSPath: imageRoot}} + + for _, imagePath := range []string{"/absolute-link", "/bin/relative-link"} { + got, err := driver.ReadFile(imagePath) + if err != nil { + t.Fatalf("ReadFile(%q): %v", imagePath, err) + } + if string(got) != "image-data" { + t.Fatalf("ReadFile(%q) = %q", imagePath, got) + } + } + + info, err := driver.StatFile("/absolute-link") + if err != nil { + t.Fatalf("StatFile on symlink: %v", err) + } + if info.Mode()&os.ModeSymlink == 0 { + t.Fatalf("StatFile should return the final symlink itself, got mode %s", info.Mode()) + } +} + +func TestTarDriverRejectsSymlinksOutsideImageRoot(t *testing.T) { + parent := t.TempDir() + imageRoot := filepath.Join(parent, "image-root") + if err := os.MkdirAll(imageRoot, 0755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(parent, "outside.txt"), []byte("outside-data"), 0644); err != nil { + t.Fatal(err) + } + if err := os.Symlink("../outside.txt", filepath.Join(imageRoot, "file-link")); err != nil { + t.Fatal(err) + } + if err := os.Symlink("..", filepath.Join(imageRoot, "dir-link")); err != nil { + t.Fatal(err) + } + + driver := &TarDriver{Image: pkgutil.Image{FSPath: imageRoot}} + + if _, err := driver.ReadFile("/file-link"); err == nil { + t.Fatal("ReadFile allowed a symlink outside the image root") + } + if _, err := driver.ReadDir("/dir-link"); err == nil { + t.Fatal("ReadDir allowed a symlink outside the image root") + } + if _, err := driver.StatFile("/dir-link/outside.txt"); err == nil { + t.Fatal("StatFile allowed an intermediate symlink outside the image root") + } +} diff --git a/pkg/output/BUILD.bazel b/pkg/output/BUILD.bazel new file mode 100644 index 00000000..4624788e --- /dev/null +++ b/pkg/output/BUILD.bazel @@ -0,0 +1,20 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "output", + srcs = ["output.go"], + importpath = "github.com/GoogleContainerTools/container-structure-test/pkg/output", + visibility = ["//visibility:public"], + deps = [ + "//pkg/color", + "//pkg/types/unversioned", + "@com_github_pkg_errors//:errors", + ], +) + +go_test( + name = "output_test", + srcs = ["output_test.go"], + embed = [":output"], + deps = ["//pkg/types/unversioned"], +) diff --git a/pkg/types/BUILD.bazel b/pkg/types/BUILD.bazel new file mode 100644 index 00000000..e12e3a18 --- /dev/null +++ b/pkg/types/BUILD.bazel @@ -0,0 +1,13 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "types", + srcs = ["types.go"], + importpath = "github.com/GoogleContainerTools/container-structure-test/pkg/types", + visibility = ["//visibility:public"], + deps = [ + "//pkg/drivers", + "//pkg/types/v1:types", + "//pkg/types/v2:types", + ], +) diff --git a/pkg/types/unversioned/BUILD.bazel b/pkg/types/unversioned/BUILD.bazel new file mode 100644 index 00000000..4c772281 --- /dev/null +++ b/pkg/types/unversioned/BUILD.bazel @@ -0,0 +1,8 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "unversioned", + srcs = ["types.go"], + importpath = "github.com/GoogleContainerTools/container-structure-test/pkg/types/unversioned", + visibility = ["//visibility:public"], +) diff --git a/pkg/types/unversioned/types.go b/pkg/types/unversioned/types.go index 84dfb602..88aa6f8f 100644 --- a/pkg/types/unversioned/types.go +++ b/pkg/types/unversioned/types.go @@ -45,13 +45,16 @@ type Config struct { } type ContainerRunOptions struct { - User string - Privileged bool - TTY bool `yaml:"allocateTty"` - EnvVars []string `yaml:"envVars"` - EnvFile string `yaml:"envFile"` - Capabilities []string - BindMounts []string `yaml:"bindMounts"` + User string + Privileged bool + TTY bool `yaml:"allocateTty"` + EnvVars []string `yaml:"envVars"` + EnvFile string `yaml:"envFile"` + Sysctls map[string]string `yaml:"sysctls"` + CapAdd []string `yaml:"capabilities"` + CapDrop []string `yaml:"drop_capabilities"` + BindMounts []string `yaml:"bindMounts"` + Network string `yaml:"network"` } func (opts *ContainerRunOptions) IsSet() bool { @@ -60,8 +63,11 @@ func (opts *ContainerRunOptions) IsSet() bool { opts.TTY || len(opts.EnvFile) > 0 || (opts.EnvVars != nil && len(opts.EnvVars) > 0) || - (opts.Capabilities != nil && len(opts.Capabilities) > 0) || - (opts.BindMounts != nil && len(opts.BindMounts) > 0) + (opts.CapAdd != nil && len(opts.CapAdd) > 0) || + (opts.CapDrop != nil && len(opts.CapDrop) > 0) || + (opts.BindMounts != nil && len(opts.BindMounts) > 0) || + len(opts.Sysctls) > 0 || + len(opts.Network) != 0 } type TestResult struct { diff --git a/pkg/types/v1/BUILD.bazel b/pkg/types/v1/BUILD.bazel new file mode 100644 index 00000000..7c2b66bf --- /dev/null +++ b/pkg/types/v1/BUILD.bazel @@ -0,0 +1,27 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "types", + srcs = [ + "command.go", + "file_content.go", + "file_existence.go", + "licenses.go", + "structure.go", + ], + importpath = "github.com/GoogleContainerTools/container-structure-test/pkg/types/v1", + visibility = ["//visibility:public"], + deps = [ + "//pkg/drivers", + "//pkg/types/unversioned", + "//pkg/utils", + "@com_github_pkg_errors//:errors", + "@com_github_sirupsen_logrus//:logrus", + ], +) + +go_test( + name = "types_test", + srcs = ["file_existence_test.go"], + embed = [":types"], +) diff --git a/pkg/types/v1/file_existence.go b/pkg/types/v1/file_existence.go index 6905d29b..9b9017fa 100644 --- a/pkg/types/v1/file_existence.go +++ b/pkg/types/v1/file_existence.go @@ -56,7 +56,7 @@ func (ft FileExistenceTest) Validate() error { return fmt.Errorf("Please provide a valid name for every test") } if ft.Path == "" { - fmt.Errorf("Please provide a valid file path for test %s", ft.Name) + return fmt.Errorf("Please provide a valid file path for test %s", ft.Name) } return nil } @@ -75,7 +75,7 @@ func (ft FileExistenceTest) Run(driver drivers.Driver) *types.TestResult { var info os.FileInfo info, err := driver.StatFile(ft.Path) if info == nil && ft.ShouldExist { - result.Errorf(errors.Wrap(err, "Error examining file in container").Error()) + result.Errorf("%s", errors.Wrap(err, "Error examining file in container").Error()) result.Fail() return result } diff --git a/pkg/types/v1/file_existence_test.go b/pkg/types/v1/file_existence_test.go new file mode 100644 index 00000000..0ddd4f37 --- /dev/null +++ b/pkg/types/v1/file_existence_test.go @@ -0,0 +1,55 @@ +// Copyright 2017 Google Inc. All rights reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v1 + +import "testing" + +func TestFileExistenceTestValidate(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + test FileExistenceTest + expectErr bool + }{ + { + name: "missing name", + test: FileExistenceTest{Path: "/foo"}, + expectErr: true, + }, + { + name: "missing path", + test: FileExistenceTest{Name: "no path"}, + expectErr: true, + }, + { + name: "valid", + test: FileExistenceTest{Name: "ok", Path: "/foo"}, + expectErr: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.test.Validate() + if tc.expectErr && err == nil { + t.Errorf("expected validation error, got nil") + } + if !tc.expectErr && err != nil { + t.Errorf("expected no error, got %v", err) + } + }) + } +} diff --git a/pkg/types/v1/licenses.go b/pkg/types/v1/licenses.go index 5a73b1ff..d0de3c47 100644 --- a/pkg/types/v1/licenses.go +++ b/pkg/types/v1/licenses.go @@ -75,7 +75,7 @@ func (lt LicenseTest) Run(driver drivers.Driver) *types.TestResult { if !p.IsDir() { continue } - logrus.Infof(p.Name()) + logrus.Info(p.Name()) // Skip over packages in the whitelist whitelisted := false for _, w := range whitelist { diff --git a/pkg/types/v2/BUILD.bazel b/pkg/types/v2/BUILD.bazel new file mode 100644 index 00000000..3bd85ae7 --- /dev/null +++ b/pkg/types/v2/BUILD.bazel @@ -0,0 +1,22 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "types", + srcs = [ + "command.go", + "file_content.go", + "file_existence.go", + "licenses.go", + "metadata.go", + "structure.go", + ], + importpath = "github.com/GoogleContainerTools/container-structure-test/pkg/types/v2", + visibility = ["//visibility:public"], + deps = [ + "//pkg/drivers", + "//pkg/types/unversioned", + "//pkg/utils", + "@com_github_pkg_errors//:errors", + "@com_github_sirupsen_logrus//:logrus", + ], +) diff --git a/pkg/types/v2/file_existence.go b/pkg/types/v2/file_existence.go index 076e9e45..1826f664 100644 --- a/pkg/types/v2/file_existence.go +++ b/pkg/types/v2/file_existence.go @@ -94,7 +94,7 @@ func (ft FileExistenceTest) Run(driver drivers.Driver) *types.TestResult { } info, err = driver.StatFile(utils.SubstituteEnvVar(ft.Path, config.Env)) if info == nil && ft.ShouldExist { - result.Errorf(errors.Wrap(err, "Error examining file in container").Error()) + result.Errorf("%s", errors.Wrap(err, "Error examining file in container").Error()) result.Fail() return result } diff --git a/pkg/types/v2/licenses.go b/pkg/types/v2/licenses.go index a715496a..8b274977 100644 --- a/pkg/types/v2/licenses.go +++ b/pkg/types/v2/licenses.go @@ -76,7 +76,7 @@ func (lt LicenseTest) Run(driver drivers.Driver) *types.TestResult { if !p.IsDir() { continue } - logrus.Debugf(p.Name()) + logrus.Debug(p.Name()) // Skip over packages in the whitelist whitelisted := false for _, w := range whitelist { diff --git a/pkg/types/v2/metadata.go b/pkg/types/v2/metadata.go index b8bacace..d043b362 100644 --- a/pkg/types/v2/metadata.go +++ b/pkg/types/v2/metadata.go @@ -180,14 +180,14 @@ func (mt MetadataTest) Run(driver drivers.Driver) *types.TestResult { } for _, port := range mt.ExposedPorts { - if !utils.ValueInList(port, imageConfig.ExposedPorts) { + if !utils.PortInList(port, imageConfig.ExposedPorts) { result.Errorf("Port %s not found in config", port) result.Fail() } } for _, port := range mt.UnexposedPorts { - if utils.ValueInList(port, imageConfig.ExposedPorts) { + if utils.PortInList(port, imageConfig.ExposedPorts) { result.Errorf("Port %s should not be exposed", port) result.Fail() } diff --git a/pkg/types/v2/metadata_test.go b/pkg/types/v2/metadata_test.go new file mode 100644 index 00000000..747cb976 --- /dev/null +++ b/pkg/types/v2/metadata_test.go @@ -0,0 +1,210 @@ +// Copyright 2017 Google Inc. All rights reserved. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package v2 + +import ( + "os" + "testing" + + types "github.com/GoogleContainerTools/container-structure-test/pkg/types/unversioned" +) + +// mockDriver provides a mock implementation of drivers.Driver for testing metadata tests +type mockDriver struct { + config *types.Config +} + +func (m *mockDriver) Setup(envVars []types.EnvVar, fullCommands [][]string) error { + return nil +} + +func (m *mockDriver) Teardown(fullCommands [][]string) error { + return nil +} + +func (m *mockDriver) SetEnv(envVars []types.EnvVar) error { + return nil +} + +func (m *mockDriver) ProcessCommand(envVars []types.EnvVar, fullCommand []string) (string, string, int, error) { + return "", "", 0, nil +} + +func (m *mockDriver) GetConfig() (types.Config, error) { + return *m.config, nil +} + +func (m *mockDriver) ReadFile(path string) ([]byte, error) { + return nil, nil +} + +func (m *mockDriver) StatFile(path string) (os.FileInfo, error) { + return nil, nil +} + +func (m *mockDriver) ReadDir(path string) ([]os.FileInfo, error) { + return nil, nil +} + +func (m *mockDriver) Destroy() { +} + +func TestExposedPortsWithProtocol(t *testing.T) { + tests := []struct { + name string + imageExposedPorts []string + testExposedPorts []string + shouldPass bool + }{ + { + name: "exact match tcp port", + imageExposedPorts: []string{"53/tcp"}, + testExposedPorts: []string{"53/tcp"}, + shouldPass: true, + }, + { + name: "exact match udp port", + imageExposedPorts: []string{"53/udp"}, + testExposedPorts: []string{"53/udp"}, + shouldPass: true, + }, + { + name: "bare port matches tcp", + imageExposedPorts: []string{"53/tcp"}, + testExposedPorts: []string{"53"}, + shouldPass: true, + }, + { + name: "bare port matches udp", + imageExposedPorts: []string{"53/udp"}, + testExposedPorts: []string{"53"}, + shouldPass: true, + }, + { + name: "protocol mismatch fails", + imageExposedPorts: []string{"53/tcp"}, + testExposedPorts: []string{"53/udp"}, + shouldPass: false, + }, + { + name: "multiple ports with protocols", + imageExposedPorts: []string{"53/tcp", "53/udp", "8080/tcp"}, + testExposedPorts: []string{"53/tcp", "53/udp"}, + shouldPass: true, + }, + { + name: "bare port with multiple exposed protocols", + imageExposedPorts: []string{"53/tcp", "53/udp"}, + testExposedPorts: []string{"53"}, + shouldPass: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockDrv := &mockDriver{ + config: &types.Config{ + Env: map[string]string{}, + ExposedPorts: tt.imageExposedPorts, + Labels: map[string]string{}, + }, + } + + metadataTest := MetadataTest{ + ExposedPorts: tt.testExposedPorts, + } + + result := metadataTest.Run(mockDrv) + + if tt.shouldPass && !result.IsPass() { + t.Errorf("expected test to pass but got errors: %v", result.Errors) + } + if !tt.shouldPass && result.IsPass() { + t.Errorf("expected test to fail but it passed") + } + }) + } +} + +func TestUnexposedPortsWithProtocol(t *testing.T) { + tests := []struct { + name string + imageExposedPorts []string + testUnexposedPorts []string + shouldPass bool + }{ + { + name: "port not exposed with exact protocol", + imageExposedPorts: []string{"8080/tcp", "9000/tcp"}, + testUnexposedPorts: []string{"53/tcp"}, + shouldPass: true, + }, + { + name: "bare port not exposed", + imageExposedPorts: []string{"8080/tcp", "9000/tcp"}, + testUnexposedPorts: []string{"53"}, + shouldPass: true, + }, + { + name: "bare port exposed fails test", + imageExposedPorts: []string{"53/tcp", "8080/tcp"}, + testUnexposedPorts: []string{"53"}, + shouldPass: false, + }, + { + name: "exact protocol port exposed fails test", + imageExposedPorts: []string{"53/tcp", "8080/tcp"}, + testUnexposedPorts: []string{"53/tcp"}, + shouldPass: false, + }, + { + name: "protocol mismatch passes unexposed test", + imageExposedPorts: []string{"53/tcp", "8080/tcp"}, + testUnexposedPorts: []string{"53/udp"}, + shouldPass: true, + }, + { + name: "multiple unexposed ports when only some exposed", + imageExposedPorts: []string{"53/tcp", "8080/tcp"}, + testUnexposedPorts: []string{"53/udp", "9000/tcp"}, + shouldPass: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockDrv := &mockDriver{ + config: &types.Config{ + Env: map[string]string{}, + ExposedPorts: tt.imageExposedPorts, + Labels: map[string]string{}, + }, + } + + metadataTest := MetadataTest{ + UnexposedPorts: tt.testUnexposedPorts, + } + + result := metadataTest.Run(mockDrv) + + if tt.shouldPass && !result.IsPass() { + t.Errorf("expected test to pass but got errors: %v", result.Errors) + } + if !tt.shouldPass && result.IsPass() { + t.Errorf("expected test to fail but it passed") + } + }) + } +} diff --git a/pkg/utils/BUILD.bazel b/pkg/utils/BUILD.bazel new file mode 100644 index 00000000..32c70adb --- /dev/null +++ b/pkg/utils/BUILD.bazel @@ -0,0 +1,9 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "utils", + srcs = ["utils.go"], + importpath = "github.com/GoogleContainerTools/container-structure-test/pkg/utils", + visibility = ["//visibility:public"], + deps = ["@com_github_sirupsen_logrus//:logrus"], +) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 4c1bb57a..34e6426c 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -18,6 +18,7 @@ import ( "fmt" "os" "regexp" + "strings" "github.com/sirupsen/logrus" ) @@ -77,6 +78,47 @@ func ValueInList(target string, list []string) bool { return false } +// PortInList checks if a port matches any port in the list, accounting for protocols. +// A port can match in the following ways: +// - exact match: "53/tcp" matches "53/tcp" +// - bare port matches any protocol: "53" matches "53/tcp" or "53/udp" +// - specific protocol must match: "53/udp" only matches "53/udp", not "53/tcp" +func PortInList(target string, list []string) bool { + // Split target into port and protocol + targetParts := strings.Split(target, "/") + targetPort := targetParts[0] + targetProto := "" + if len(targetParts) > 1 { + targetProto = targetParts[1] + } + + for _, value := range list { + // Split list value into port and protocol + valueParts := strings.Split(value, "/") + valuePort := valueParts[0] + valueProto := "" + if len(valueParts) > 1 { + valueProto = valueParts[1] + } + + // Check if ports match + if targetPort != valuePort { + continue + } + + // If target has no protocol (bare port), match any protocol + if targetProto == "" { + return true + } + + // If target has protocol, must match exactly + if targetProto == valueProto { + return true + } + } + return false +} + func SubstituteEnvVar(arg string, env map[string]string) string { f := func(key string) string { return env[key] diff --git a/pkg/version/BUILD.bazel b/pkg/version/BUILD.bazel new file mode 100644 index 00000000..5f318e8d --- /dev/null +++ b/pkg/version/BUILD.bazel @@ -0,0 +1,8 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "version", + srcs = ["version.go"], + importpath = "github.com/GoogleContainerTools/container-structure-test/pkg/version", + visibility = ["//visibility:public"], +) diff --git a/tests/amd64/fluent_bit_test.yaml b/tests/amd64/fluent_bit_test.yaml new file mode 100644 index 00000000..a8102751 --- /dev/null +++ b/tests/amd64/fluent_bit_test.yaml @@ -0,0 +1,19 @@ +schemaVersion: '2.0.0' # Make sure to test the latest schema version +commandTests: +- name: 'fluent-bit' + command: '/fluent-bit/bin/fluent-bit' + args: ['--version'] + expectedOutput: ['Fluent Bit v4.0.4'] +fileContentTests: +- name: 'Passwd file' + expectedContents: ['root:x:0:0:root:/root:/sbin/nologin'] + path: '/etc/passwd' +fileExistenceTests: +- name: 'Root' + path: '/' + shouldExist: true + uid: 0 + gid: 0 +- name: 'libc' + path: '/lib/x86_64-linux-gnu/libc.so.6' + shouldExist: true diff --git a/tests/amd64/ubuntu_22_04_containeropts_network_host_test.yaml b/tests/amd64/ubuntu_22_04_containeropts_network_host_test.yaml new file mode 100644 index 00000000..81437bb0 --- /dev/null +++ b/tests/amd64/ubuntu_22_04_containeropts_network_host_test.yaml @@ -0,0 +1,8 @@ +schemaVersion: "2.0.0" +containerRunOptions: + network: "host" +commandTests: + - name: "network host exposes host network interfaces" + command: "cat" + args: ["/proc/net/dev"] + expectedOutput: ["eth0|en[a-z0-9]+|ens|eno|wlan"] diff --git a/tests/amd64/ubuntu_22_04_containeropts_network_none_test.yaml b/tests/amd64/ubuntu_22_04_containeropts_network_none_test.yaml new file mode 100644 index 00000000..a5d3c66d --- /dev/null +++ b/tests/amd64/ubuntu_22_04_containeropts_network_none_test.yaml @@ -0,0 +1,8 @@ +schemaVersion: "2.0.0" +containerRunOptions: + network: "none" +commandTests: + - name: "network none test" + command: "getent" + args: ["hosts", "google.com"] + exitCode: 2 diff --git a/tests/amd64/ubuntu_22_04_containeropts_test.yaml b/tests/amd64/ubuntu_22_04_containeropts_test.yaml index 28d78dcd..b88b8162 100644 --- a/tests/amd64/ubuntu_22_04_containeropts_test.yaml +++ b/tests/amd64/ubuntu_22_04_containeropts_test.yaml @@ -5,6 +5,10 @@ commandTests: args: ["--print"] expectedOutput: - ".*cap_sys_admin.*" + excludedOutput: + # match chown only where it is GRANTED (e.g. ",cap_chown"), not capsh's + # "!cap_chown" marker that indicates it was dropped + - "[^!]cap_chown" - name: "Test bindMounts containerRunOptions" command: "test" args: @@ -12,8 +16,9 @@ commandTests: - "/tmp/test" exitCode: 0 containerRunOptions: - privileged: true capabilities: - "sys_admin" + drop_capabilities: + - "chown" bindMounts: - "/tmp/test:/tmp/test" diff --git a/tests/amd64/ubuntu_22_04_tar_test.yaml b/tests/amd64/ubuntu_22_04_tar_test.yaml new file mode 100644 index 00000000..3d908e52 --- /dev/null +++ b/tests/amd64/ubuntu_22_04_tar_test.yaml @@ -0,0 +1,24 @@ +schemaVersion: '2.0.0' +fileContentTests: +- name: 'Debian Sources' + excludedContents: ['.*gce_debian_mirror.*'] + expectedContents: ['.*archive\.ubuntu\.com.*'] + path: '/etc/apt/sources.list' +- name: 'Passwd file' + expectedContents: ['root:x:0:0:root:/root:/bin/bash'] + path: '/etc/passwd' +fileExistenceTests: +- name: 'Date' + path: '/bin/date' + isExecutableBy: 'owner' +- name: 'Hosts File' + path: '/etc/hosts' + shouldExist: true +- name: 'Dummy File' + path: '/etc/dummy' + shouldExist: false +licenseTests: +- debian: false + files: + - "/usr/share/doc/ubuntu-keyring/copyright" + - "/usr/share/doc/dash/copyright" diff --git a/tests/arm64/fluent_bit_test.yaml b/tests/arm64/fluent_bit_test.yaml new file mode 100644 index 00000000..da48a54a --- /dev/null +++ b/tests/arm64/fluent_bit_test.yaml @@ -0,0 +1,19 @@ +schemaVersion: '2.0.0' # Make sure to test the latest schema version +commandTests: +- name: 'fluent-bit' + command: '/fluent-bit/bin/fluent-bit' + args: ['--version'] + expectedOutput: ['Fluent Bit v4.0.4'] +fileContentTests: +- name: 'Passwd file' + expectedContents: ['root:x:0:0:root:/root:/sbin/nologin'] + path: '/etc/passwd' +fileExistenceTests: +- name: 'Root' + path: '/' + shouldExist: true + uid: 0 + gid: 0 +- name: 'libc' + path: '/lib/aarch64-linux-gnu/libc.so.6' + shouldExist: true diff --git a/tests/arm64/ubuntu_22_04_containeropts_network_host_test.yaml b/tests/arm64/ubuntu_22_04_containeropts_network_host_test.yaml new file mode 100644 index 00000000..81437bb0 --- /dev/null +++ b/tests/arm64/ubuntu_22_04_containeropts_network_host_test.yaml @@ -0,0 +1,8 @@ +schemaVersion: "2.0.0" +containerRunOptions: + network: "host" +commandTests: + - name: "network host exposes host network interfaces" + command: "cat" + args: ["/proc/net/dev"] + expectedOutput: ["eth0|en[a-z0-9]+|ens|eno|wlan"] diff --git a/tests/arm64/ubuntu_22_04_containeropts_network_none_test.yaml b/tests/arm64/ubuntu_22_04_containeropts_network_none_test.yaml new file mode 100644 index 00000000..a5d3c66d --- /dev/null +++ b/tests/arm64/ubuntu_22_04_containeropts_network_none_test.yaml @@ -0,0 +1,8 @@ +schemaVersion: "2.0.0" +containerRunOptions: + network: "none" +commandTests: + - name: "network none test" + command: "getent" + args: ["hosts", "google.com"] + exitCode: 2 diff --git a/tests/arm64/ubuntu_22_04_containeropts_test.yaml b/tests/arm64/ubuntu_22_04_containeropts_test.yaml index 28d78dcd..b88b8162 100644 --- a/tests/arm64/ubuntu_22_04_containeropts_test.yaml +++ b/tests/arm64/ubuntu_22_04_containeropts_test.yaml @@ -5,6 +5,10 @@ commandTests: args: ["--print"] expectedOutput: - ".*cap_sys_admin.*" + excludedOutput: + # match chown only where it is GRANTED (e.g. ",cap_chown"), not capsh's + # "!cap_chown" marker that indicates it was dropped + - "[^!]cap_chown" - name: "Test bindMounts containerRunOptions" command: "test" args: @@ -12,8 +16,9 @@ commandTests: - "/tmp/test" exitCode: 0 containerRunOptions: - privileged: true capabilities: - "sys_admin" + drop_capabilities: + - "chown" bindMounts: - "/tmp/test:/tmp/test" diff --git a/tests/arm64/ubuntu_22_04_tar_test.yaml b/tests/arm64/ubuntu_22_04_tar_test.yaml new file mode 100644 index 00000000..f6d50dae --- /dev/null +++ b/tests/arm64/ubuntu_22_04_tar_test.yaml @@ -0,0 +1,24 @@ +schemaVersion: '2.0.0' +fileContentTests: +- name: 'Debian Sources' + excludedContents: ['.*gce_debian_mirror.*'] + expectedContents: ['.*ports\.ubuntu\.com.*'] + path: '/etc/apt/sources.list' +- name: 'Passwd file' + expectedContents: ['root:x:0:0:root:/root:/bin/bash'] + path: '/etc/passwd' +fileExistenceTests: +- name: 'Date' + path: '/bin/date' + isExecutableBy: 'owner' +- name: 'Hosts File' + path: '/etc/hosts' + shouldExist: true +- name: 'Dummy File' + path: '/etc/dummy' + shouldExist: false +licenseTests: +- debian: false + files: + - "/usr/share/doc/ubuntu-keyring/copyright" + - "/usr/share/doc/dash/copyright" diff --git a/tests/ppc64le/ubuntu_22_04_containeropts_network_host_test.yaml b/tests/ppc64le/ubuntu_22_04_containeropts_network_host_test.yaml new file mode 100644 index 00000000..81437bb0 --- /dev/null +++ b/tests/ppc64le/ubuntu_22_04_containeropts_network_host_test.yaml @@ -0,0 +1,8 @@ +schemaVersion: "2.0.0" +containerRunOptions: + network: "host" +commandTests: + - name: "network host exposes host network interfaces" + command: "cat" + args: ["/proc/net/dev"] + expectedOutput: ["eth0|en[a-z0-9]+|ens|eno|wlan"] diff --git a/tests/ppc64le/ubuntu_22_04_containeropts_network_none_test.yaml b/tests/ppc64le/ubuntu_22_04_containeropts_network_none_test.yaml new file mode 100644 index 00000000..a5d3c66d --- /dev/null +++ b/tests/ppc64le/ubuntu_22_04_containeropts_network_none_test.yaml @@ -0,0 +1,8 @@ +schemaVersion: "2.0.0" +containerRunOptions: + network: "none" +commandTests: + - name: "network none test" + command: "getent" + args: ["hosts", "google.com"] + exitCode: 2 diff --git a/tests/ppc64le/ubuntu_22_04_containeropts_test.yaml b/tests/ppc64le/ubuntu_22_04_containeropts_test.yaml index 28d78dcd..b88b8162 100644 --- a/tests/ppc64le/ubuntu_22_04_containeropts_test.yaml +++ b/tests/ppc64le/ubuntu_22_04_containeropts_test.yaml @@ -5,6 +5,10 @@ commandTests: args: ["--print"] expectedOutput: - ".*cap_sys_admin.*" + excludedOutput: + # match chown only where it is GRANTED (e.g. ",cap_chown"), not capsh's + # "!cap_chown" marker that indicates it was dropped + - "[^!]cap_chown" - name: "Test bindMounts containerRunOptions" command: "test" args: @@ -12,8 +16,9 @@ commandTests: - "/tmp/test" exitCode: 0 containerRunOptions: - privileged: true capabilities: - "sys_admin" + drop_capabilities: + - "chown" bindMounts: - "/tmp/test:/tmp/test" diff --git a/tests/ppc64le/ubuntu_22_04_tar_test.yaml b/tests/ppc64le/ubuntu_22_04_tar_test.yaml new file mode 100644 index 00000000..f6d50dae --- /dev/null +++ b/tests/ppc64le/ubuntu_22_04_tar_test.yaml @@ -0,0 +1,24 @@ +schemaVersion: '2.0.0' +fileContentTests: +- name: 'Debian Sources' + excludedContents: ['.*gce_debian_mirror.*'] + expectedContents: ['.*ports\.ubuntu\.com.*'] + path: '/etc/apt/sources.list' +- name: 'Passwd file' + expectedContents: ['root:x:0:0:root:/root:/bin/bash'] + path: '/etc/passwd' +fileExistenceTests: +- name: 'Date' + path: '/bin/date' + isExecutableBy: 'owner' +- name: 'Hosts File' + path: '/etc/hosts' + shouldExist: true +- name: 'Dummy File' + path: '/etc/dummy' + shouldExist: false +licenseTests: +- debian: false + files: + - "/usr/share/doc/ubuntu-keyring/copyright" + - "/usr/share/doc/dash/copyright" diff --git a/tests/s390x/ubuntu_22_04_containeropts_network_host_test.yaml b/tests/s390x/ubuntu_22_04_containeropts_network_host_test.yaml new file mode 100644 index 00000000..81437bb0 --- /dev/null +++ b/tests/s390x/ubuntu_22_04_containeropts_network_host_test.yaml @@ -0,0 +1,8 @@ +schemaVersion: "2.0.0" +containerRunOptions: + network: "host" +commandTests: + - name: "network host exposes host network interfaces" + command: "cat" + args: ["/proc/net/dev"] + expectedOutput: ["eth0|en[a-z0-9]+|ens|eno|wlan"] diff --git a/tests/s390x/ubuntu_22_04_containeropts_network_none_test.yaml b/tests/s390x/ubuntu_22_04_containeropts_network_none_test.yaml new file mode 100644 index 00000000..a5d3c66d --- /dev/null +++ b/tests/s390x/ubuntu_22_04_containeropts_network_none_test.yaml @@ -0,0 +1,8 @@ +schemaVersion: "2.0.0" +containerRunOptions: + network: "none" +commandTests: + - name: "network none test" + command: "getent" + args: ["hosts", "google.com"] + exitCode: 2 diff --git a/tests/s390x/ubuntu_22_04_containeropts_test.yaml b/tests/s390x/ubuntu_22_04_containeropts_test.yaml index 28d78dcd..b88b8162 100644 --- a/tests/s390x/ubuntu_22_04_containeropts_test.yaml +++ b/tests/s390x/ubuntu_22_04_containeropts_test.yaml @@ -5,6 +5,10 @@ commandTests: args: ["--print"] expectedOutput: - ".*cap_sys_admin.*" + excludedOutput: + # match chown only where it is GRANTED (e.g. ",cap_chown"), not capsh's + # "!cap_chown" marker that indicates it was dropped + - "[^!]cap_chown" - name: "Test bindMounts containerRunOptions" command: "test" args: @@ -12,8 +16,9 @@ commandTests: - "/tmp/test" exitCode: 0 containerRunOptions: - privileged: true capabilities: - "sys_admin" + drop_capabilities: + - "chown" bindMounts: - "/tmp/test:/tmp/test" diff --git a/tests/s390x/ubuntu_22_04_tar_test.yaml b/tests/s390x/ubuntu_22_04_tar_test.yaml new file mode 100644 index 00000000..f6d50dae --- /dev/null +++ b/tests/s390x/ubuntu_22_04_tar_test.yaml @@ -0,0 +1,24 @@ +schemaVersion: '2.0.0' +fileContentTests: +- name: 'Debian Sources' + excludedContents: ['.*gce_debian_mirror.*'] + expectedContents: ['.*ports\.ubuntu\.com.*'] + path: '/etc/apt/sources.list' +- name: 'Passwd file' + expectedContents: ['root:x:0:0:root:/root:/bin/bash'] + path: '/etc/passwd' +fileExistenceTests: +- name: 'Date' + path: '/bin/date' + isExecutableBy: 'owner' +- name: 'Hosts File' + path: '/etc/hosts' + shouldExist: true +- name: 'Dummy File' + path: '/etc/dummy' + shouldExist: false +licenseTests: +- debian: false + files: + - "/usr/share/doc/ubuntu-keyring/copyright" + - "/usr/share/doc/dash/copyright" diff --git a/tests/structure_test_tests.sh b/tests/structure_test_tests.sh index 87fac56a..6929b6a4 100755 --- a/tests/structure_test_tests.sh +++ b/tests/structure_test_tests.sh @@ -144,6 +144,28 @@ else echo "PASS: Run options (envFile) test case passed" fi +res=$(./out/container-structure-test test --image "$test_image" --config "${test_config_dir}/ubuntu_22_04_containeropts_network_none_test.yaml") +code=$? +if ! [[ ("$res" =~ "PASS" && "$code" == "0") ]]; +then + echo "FAIL: Run options (network none) test case failed" + echo "$res" + failures=$((failures +1)) +else + echo "PASS: Run options (network none) test case passed" +fi + +res=$(./out/container-structure-test test --image "$test_image" --config "${test_config_dir}/ubuntu_22_04_containeropts_network_host_test.yaml") +code=$? +if ! [[ ("$res" =~ "PASS" && "$code" == "0") ]]; +then + echo "FAIL: Run options (network host) test case failed" + echo "$res" + failures=$((failures +1)) +else + echo "PASS: Run options (network host) test case passed" +fi + HEADER "Metadata Test Case" # test image metadata @@ -246,44 +268,102 @@ else echo "PASS: oci success test case" fi -HEADER "Platform test cases" +HEADER "OCI multi-arch image index test case" -docker run --rm --privileged tonistiigi/binfmt --install all > /dev/null -res=$(./out/container-structure-test test --image "$test_image" --platform="linux/$go_architecture" --config "${test_config_dir}/ubuntu_22_04_test.yaml" 2>&1) -code=$? -if ! [[ ("$res" =~ "PASS" && "$code" == "0") ]]; -then - echo "FAIL: current host platform test case" - echo "$res" - echo "$code" - failures=$((failures +1)) +if [[ "$go_architecture" == "amd64" || "$go_architecture" == "arm64" ]]; then + test_index="cr.fluentbit.io/fluent/fluent-bit:4.0.4" + + manifest=$(docker manifest inspect "$test_index") + if [[ $(echo "$manifest" | jq '.mediaType') != '"application/vnd.docker.distribution.manifest.list.v2+json"' ]]; then + echo "FAIL: multi-arch image index test case - $test_index is not an image index" + echo "$manifest" | jq '.mediaType' + failures=$((failures +1)) + fi + image_count=$(echo "$manifest" | jq '.manifests | length') + if [[ $image_count -lt 2 ]]; then + echo "FAIL: multi-arch image index test case - $test_index only has $image_count image(s)" + failures=$((failures +1)) + fi + + tmp_index="$(mktemp -d)" + crane pull "$test_index" --format=oci "$tmp_index" + + res=$(./out/container-structure-test test --image-from-oci-layout="$tmp_index" --default-image-tag="test.local/$test_index" --config "${test_config_dir}/fluent_bit_test.yaml" 2>&1) + code=$? + if ! [[ ("$res" =~ "PASS" && "$code" == "0") ]]; + then + echo "FAIL: oci image index success test case" + echo "$res" + echo "$code" + failures=$((failures +1)) + else + echo "PASS: oci image index test case" + fi else - echo "PASS: current host platform test case" + echo "SKIP: oci image index test case not supported on $go_architecture" fi -res=$(./out/container-structure-test test --image "$test_image" --platform="linux/riscv64" --config "${test_config_dir}/ubuntu_22_04_test.yaml" 2>&1) +HEADER "OCI layout with tar driver test case" + +res=$(./out/container-structure-test test --driver tar --image-from-oci-layout="$tmp" --config "${test_config_dir}/ubuntu_22_04_tar_test.yaml" 2>&1) code=$? -if ! [[ "$res" =~ image\ with\ reference.+was\ found\ but\ its\ platform\ \(linux\/${go_architecture}\)\ does\ not\ match\ the\ specified\ platform\ \(linux\/riscv64\) && "$code" == "1" ]]; +if ! [[ ("$res" =~ "PASS" && "$code" == "0") ]]; then - echo "FAIL: platform failing test case" + echo "FAIL: oci layout with tar driver test case" echo "$res" echo "$code" failures=$((failures +1)) else - echo "PASS: platform failing test case" + echo "PASS: oci layout with tar driver test case" fi -test_config_dir="${test_dir}/s390x" -res=$(./out/container-structure-test test --image "$test_image" --platform="linux/s390x" --pull --config "${test_config_dir}/ubuntu_22_04_test.yaml" 2>&1) +rm -rf "$tmp" + +HEADER "Platform test cases" + +# Native-platform test runs everywhere; the host arch needs no emulation. +res=$(./out/container-structure-test test --image "$test_image" --platform="linux/$go_architecture" --config "${test_config_dir}/ubuntu_22_04_test.yaml" 2>&1) code=$? if ! [[ ("$res" =~ "PASS" && "$code" == "0") ]]; then - echo "FAIL: platform w/ --pull test case" + echo "FAIL: current host platform test case" echo "$res" echo "$code" failures=$((failures +1)) else - echo "PASS: platform w/ --pull test case" + echo "PASS: current host platform test case" +fi + +# Cross-platform (riscv64/s390x) cases need QEMU/binfmt emulation, a multi-platform +# image store, and assert the Linux daemon's exact error wording. colima's Docker on +# macOS provides none of these, so these run on Linux only. +if [[ "$(uname -s)" == "Linux" ]]; then + docker run --rm --privileged tonistiigi/binfmt --install all > /dev/null + + res=$(./out/container-structure-test test --image "$test_image" --platform="linux/riscv64" --config "${test_config_dir}/ubuntu_22_04_test.yaml" 2>&1) + code=$? + if ! [[ "$res" =~ image\ with\ reference.+was\ found\ but\ its\ platform\ \(linux\/${go_architecture}\)\ does\ not\ match\ the\ specified\ platform\ \(linux\/riscv64\) && "$code" == "1" ]]; + then + echo "FAIL: platform failing test case" + echo "$res" + echo "$code" + failures=$((failures +1)) + else + echo "PASS: platform failing test case" + fi + + test_config_dir="${test_dir}/s390x" + res=$(./out/container-structure-test test --image "$test_image" --platform="linux/s390x" --pull --config "${test_config_dir}/ubuntu_22_04_test.yaml" 2>&1) + code=$? + if ! [[ ("$res" =~ "PASS" && "$code" == "0") ]]; + then + echo "FAIL: platform w/ --pull test case" + echo "$res" + echo "$code" + failures=$((failures +1)) + else + echo "PASS: platform w/ --pull test case" + fi fi diff --git a/testutil/BUILD.bazel b/testutil/BUILD.bazel new file mode 100644 index 00000000..ab1abe34 --- /dev/null +++ b/testutil/BUILD.bazel @@ -0,0 +1,9 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "testutil", + srcs = ["util.go"], + importpath = "github.com/GoogleContainerTools/container-structure-test/testutil", + visibility = ["//visibility:public"], + deps = ["@com_github_google_go_cmp//cmp"], +)