main bab6ba66e024 cached
204 files
402.4 KB
145.1k tokens
26 symbols
1 requests
Download .txt
Showing preview only (445K chars total). Download the full file or copy to clipboard to get everything.
Repository: GoogleContainerTools/distroless
Branch: main
Commit: bab6ba66e024
Files: 204
Total size: 402.4 KB

Directory structure:
gitextract_vcjsnv1p/

├── .bazelignore
├── .bazelrc
├── .bazelversion
├── .cloudbuild/
│   ├── cloudbuild.yaml
│   ├── lifecycle_tag.sh
│   ├── lifecycle_tag.yaml
│   └── release.sh
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── actionable-debian-cve.md
│   │   ├── actionable-non-debian-cve.md
│   │   └── bug_report.md
│   ├── dependabot.yml
│   └── workflows/
│       ├── buildifier.yaml
│       ├── check-ldconfig.yaml
│       ├── ci.bazelrc
│       ├── ci.yaml
│       ├── config-diff.yaml
│       ├── examples.yaml
│       ├── image-check.yaml
│       ├── scorecards-analysis.yml
│       ├── update-deb-package-non-snapshots.yml
│       ├── update-deb-package-snapshots.yml
│       └── update-node-archives.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .prettierignore
├── BUILD
├── CONTRIBUTING.md
├── LICENSE
├── MODULE.bazel
├── PACKAGE_METADATA.md
├── README.md
├── RELEASES.md
├── SECURITY.md
├── SUPPORT_POLICY.md
├── WORKSPACE
├── base/
│   ├── BUILD
│   ├── README.md
│   ├── base.bzl
│   ├── config.bzl
│   ├── test.sh
│   └── testdata/
│       ├── base.yaml
│       └── debug.yaml
├── cc/
│   ├── BUILD
│   ├── README.md
│   ├── cc.bzl
│   └── config.bzl
├── common/
│   ├── BUILD.bazel
│   └── variables.bzl
├── cosign.pub
├── distro.bzl
├── downloader.cfg
├── examples/
│   ├── BUILD
│   ├── cc/
│   │   ├── BUILD
│   │   ├── Dockerfile
│   │   ├── hello.c
│   │   ├── hello_cc.cc
│   │   └── testdata/
│   │       ├── hello_cc_debian12.yaml
│   │       ├── hello_cc_debian13.yaml
│   │       ├── hello_debian12.yaml
│   │       └── hello_debian13.yaml
│   ├── go/
│   │   ├── BUILD
│   │   ├── Dockerfile
│   │   ├── go.mod
│   │   ├── main.go
│   │   └── main_test.go
│   ├── java/
│   │   ├── BUILD
│   │   ├── Dockerfile
│   │   ├── HelloJava.java
│   │   └── testdata/
│   │       ├── hello_nonroot_debian13.yaml
│   │       └── hello_root_debian13.yaml
│   ├── nodejs/
│   │   ├── BUILD
│   │   ├── Dockerfile
│   │   ├── hello.js
│   │   ├── hello_http.js
│   │   ├── node-express/
│   │   │   ├── Dockerfile
│   │   │   ├── hello_express.js
│   │   │   └── package.json
│   │   ├── package.json
│   │   └── testdata/
│   │       └── hello.yaml
│   ├── nonroot/
│   │   ├── BUILD
│   │   └── testdata/
│   │       ├── user.go
│   │       └── user.yaml
│   ├── python3/
│   │   ├── BUILD
│   │   ├── Dockerfile
│   │   └── hello.py
│   ├── python3-requirements/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── psutil_example.py
│   │   └── requirements.txt
│   ├── rust/
│   │   ├── .dockerignore
│   │   ├── BUILD
│   │   ├── Cargo.toml
│   │   ├── Dockerfile
│   │   └── src/
│   │       └── main.rs
│   └── test.sh
├── experimental/
│   ├── BUILD
│   └── busybox/
│       ├── BUILD
│       └── commands.bzl
├── java/
│   ├── BUILD
│   ├── README.md
│   ├── config.bzl
│   ├── control
│   ├── java.bzl
│   ├── jre_ver.bzl
│   └── testdata/
│       ├── CheckCerts.java
│       ├── CheckEncoding.java
│       ├── CheckLibharfbuzz.java
│       ├── java17_debian13.yaml
│       ├── java17_debug_debian13.yaml
│       ├── java21_debian13.yaml
│       ├── java21_debug_debian13.yaml
│       ├── java25_debian13.yaml
│       ├── java25_debug_debian13.yaml
│       ├── java_base.yaml
│       ├── java_base_debug.yaml
│       ├── java_certs.yaml
│       ├── java_encoding.yaml
│       └── java_libharfbuzz.yaml
├── knife
├── knife.d/
│   ├── update_java_versions.sh
│   └── update_node_archives.js
├── nodejs/
│   ├── BUILD
│   ├── README.md
│   ├── config.bzl
│   ├── control
│   ├── nodejs.bzl
│   └── testdata/
│       ├── check_certificate.js
│       ├── check_certificate.yaml
│       ├── check_headers.yaml
│       ├── check_npm.yaml
│       ├── nodejs20.yaml
│       ├── nodejs22.yaml
│       └── nodejs24.yaml
├── private/
│   ├── extensions/
│   │   ├── BUILD.bazel
│   │   ├── busybox.bzl
│   │   ├── node.bzl
│   │   └── version.bzl
│   ├── oci/
│   │   ├── BUILD.bazel
│   │   ├── cc_image.bzl
│   │   ├── defs.bzl
│   │   ├── digest.bzl
│   │   ├── go_image.bzl
│   │   ├── java_image.bzl
│   │   ├── rust_image.bzl
│   │   ├── sign_and_push.bzl
│   │   └── sign_and_push.sh.tpl
│   ├── pkg/
│   │   ├── BUILD.bazel
│   │   ├── debian_spdx.bzl
│   │   ├── debian_spdx.go
│   │   ├── oci_image_spdx.bzl
│   │   ├── oci_image_spdx.go
│   │   └── test/
│   │       └── oci_image/
│   │           ├── BUILD.bazel
│   │           ├── fat_image_sbom.spdx.json
│   │           ├── image_amd64.spdx.json
│   │           └── image_arm64.spdx.json
│   ├── repos/
│   │   └── deb/
│   │       ├── BUILD.bazel
│   │       ├── bookworm.lock.json
│   │       ├── bookworm.yaml
│   │       ├── bookworm_python.lock.json
│   │       ├── bookworm_python.yaml
│   │       ├── deb.MODULE.bazel
│   │       ├── package.BUILD.tmpl
│   │       ├── trixie.lock.json
│   │       ├── trixie.yaml
│   │       ├── trixie_adoptium.lock.json
│   │       ├── trixie_adoptium.yaml
│   │       ├── trixie_java.lock.json
│   │       ├── trixie_java.yaml
│   │       ├── trixie_python.lock.json
│   │       └── trixie_python.yaml
│   ├── stamp.bash
│   ├── tools/
│   │   ├── diff/
│   │   │   ├── BUILD.bazel
│   │   │   └── diff.bash
│   │   └── lifecycle/
│   │       ├── BUILD.bazel
│   │       ├── defs.bzl
│   │       ├── tag.bzl
│   │       ├── tag.sh
│   │       └── tag.sh.README.md
│   └── util/
│       ├── BUILD
│       ├── deb.bzl
│       ├── extract.bzl
│       ├── java_cacerts.bzl
│       ├── merge_providers.bzl
│       └── tar.bzl
├── python3/
│   ├── BUILD
│   ├── README.md
│   ├── config.bzl
│   ├── ldconfig/
│   │   ├── ld.so.cache.amd64
│   │   ├── ld.so.cache.arm64
│   │   └── ldconfig.sh
│   ├── ldconfig.bzl
│   ├── python.bzl
│   └── testdata/
│       ├── debian12.yaml
│       ├── debian13.yaml
│       └── python3.yaml
└── static/
    ├── BUILD
    ├── config.bzl
    ├── nsswitch.conf
    ├── static.bzl
    └── testdata/
        ├── check_certs.go
        ├── debian12.yaml
        ├── debian13.yaml
        ├── debug.yaml
        └── static.yaml

================================================
FILE CONTENTS
================================================

================================================
FILE: .bazelignore
================================================


================================================
FILE: .bazelrc
================================================
# Bazel settings that apply to this repository.
# Take care to document any settings that you expect users to apply.
# Settings that apply only to CI are in .github/workflows/ci.bazelrc

# Allow DOCKER_HOST env to leak into test actions.
test --test_env=DOCKER_HOST

# Stamp
build:release --workspace_status_command=./private/stamp.bash --stamp
run:release --workspace_status_command=./private/stamp.bash --stamp
test:release --workspace_status_command=./private/stamp.bash --stamp

# Allow external dependencies to be retried. debian snapshot is unreliable and needs retries.
common --experimental_repository_downloader_retries=20
common --experimental_downloader_config=downloader.cfg
common --http_timeout_scaling=2.0

# Enable platform specific options
build --enable_platform_specific_config

# Use a hermetic Java version
build --java_runtime_version=remotejdk_11

# Newer versions jdk creates collisions on /tmp
# See: https://github.com/bazelbuild/bazel/issues/3236
# https://github.com/GoogleContainerTools/rules_distroless/actions/runs/7118944984/job/19382981899?pr=9#step:8:51
common:linux --sandbox_tmpfs_path=/tmp

# Load any settings specific to the current user.
# .bazelrc.user should appear in .gitignore so that settings are not shared with team members
# This needs to be last statement in this
# config, as the user configuration should be able to overwrite flags from this file.
# See https://docs.bazel.build/versions/master/best-practices.html#bazelrc
# (Note that we use .bazelrc.user so the file appears next to .bazelrc in directory listing,
# rather than user.bazelrc as suggested in the Bazel docs)
try-import %workspace%/.bazelrc.user


================================================
FILE: .bazelversion
================================================
7.4.0

================================================
FILE: .cloudbuild/cloudbuild.yaml
================================================
timeout: 10800s  # 3 hours

options:
  machineType: E2_HIGHCPU_32
  logging: CLOUD_LOGGING_ONLY

artifacts:
  objects:
    location: 'gs://${_ARTIFACTS_GCS_}/logs'
    paths: ['.logs/*.log']

steps:
  - name: gcr.io/cloud-builders/bazel@sha256:70e96d9faec4bab40a9d8d55d6b86ce2657927d8bca6bdc2dcb21a82b66dbdf7 # 5.4.0
    env:
      - PROJECT_ID=${PROJECT_ID}
      - COMMIT_SHA=${COMMIT_SHA}
      - REGISTRY=gcr.io
      - REMOTE_CACHE_GCS=${_REMOTE_CACHE_GCS_}
      - KEYLESS=keyless@${PROJECT_ID}.iam.gserviceaccount.com
    entrypoint: bash
    args: [".cloudbuild/release.sh"]


================================================
FILE: .cloudbuild/lifecycle_tag.sh
================================================
#!/usr/bin/env bash
set -o errexit -o nounset -o xtrace -o pipefail

BAZELISK_VERSION="1.27.0"
BAZELISK_SHA256="e1508323f347ad1465a887bc5d2bfb91cffc232d11e8e997b623227c6b32fb76"

GGCR_VERSION="0.20.7"
GGCR_SHA256="8ef3564d264e6b5ca93f7b7f5652704c4dd29d33935aff6947dd5adefd05953e"

# install gcrane
curl -fsSL "https://github.com/google/go-containerregistry/releases/download/v${GGCR_VERSION}/go-containerregistry_Linux_x86_64.tar.gz" -o ggcr.tar.gz
echo "${GGCR_SHA256} ggcr.tar.gz" | sha256sum --check
tar -xzf ggcr.tar.gz gcrane
chmod a+x gcrane
mv gcrane /usr/bin/gcrane # needs to be on path

# we need jq too
apt-get update
apt-get -y install jq

# install bazelisk (TODO: there's probably a better way to do this)
curl -fsSL "https://github.com/bazelbuild/bazelisk/releases/download/v${BAZELISK_VERSION}/bazelisk-linux-amd64" -o bazelisk
echo "${BAZELISK_SHA256} bazelisk" | sha256sum --check
chmod a+x bazelisk

echo "common --google_default_credentials" >> ~/.bazelrc
echo "common --announce_rc" >> ~/.bazelrc

./bazelisk run :attach_lifecycle_tags --config=release


================================================
FILE: .cloudbuild/lifecycle_tag.yaml
================================================
timeout: 1800s # 30 minutes

options:
  machineType: E2_MEDIUM
  logging: CLOUD_LOGGING_ONLY

steps:
  - name: gcr.io/cloud-builders/bazel@sha256:70e96d9faec4bab40a9d8d55d6b86ce2657927d8bca6bdc2dcb21a82b66dbdf7 # 5.4.0
    env:
      - PROJECT_ID=${PROJECT_ID}
      - REGISTRY=gcr.io
    entrypoint: bash
    args: [".cloudbuild/lifecycle_tag.sh"]


================================================
FILE: .cloudbuild/release.sh
================================================
#!/usr/bin/env bash
set -o errexit -o xtrace -o pipefail

BAZEL_REMOTE_VERSION="2.6.1"
BAZEL_REMOTE_SHA256="025d53aeb03a7fdd4a0e76262a5ae9eeee9f64d53ca510deff1c84cf3f276784"
BAZELISK_VERSION="1.27.0"
BAZELISK_SHA256="e1508323f347ad1465a887bc5d2bfb91cffc232d11e8e997b623227c6b32fb76"

# setup remote cache
curl -fsSL "https://github.com/buchgr/bazel-remote/releases/download/v${BAZEL_REMOTE_VERSION}/bazel-remote-${BAZEL_REMOTE_VERSION}-linux-amd64" -o bazel-remote
echo "${BAZEL_REMOTE_SHA256} bazel-remote" | sha256sum --check
chmod +x bazel-remote
mkdir .logs
./bazel-remote --max_size 8 --dir ~/.cache/bazel-remote --experimental_remote_asset_api --grpc_address 0.0.0.0:4700 --gcs_proxy.bucket $REMOTE_CACHE_GCS --gcs_proxy.use_default_credentials > .logs/bazel-remote.log 2>&1 &

# install bazelisk (TODO: there's probably a better way to do this)
curl -fsSL "https://github.com/bazelbuild/bazelisk/releases/download/v${BAZELISK_VERSION}/bazelisk-linux-amd64" -o bazelisk
echo "${BAZELISK_SHA256} bazelisk" | sha256sum --check
chmod a+x bazelisk

# setup remote caching and remote asset API.
echo "common --remote_cache=grpc://0.0.0.0:4700" >> ~/.bazelrc
echo "common --experimental_remote_downloader=grpc://0.0.0.0:4700" >> ~/.bazelrc
echo "common --google_default_credentials" >> ~/.bazelrc
echo "common --announce_rc" >> ~/.bazelrc

for i in $(seq 5); do 
    ./bazelisk cquery 'kind(merge_providers, deps(kind(oci_image, ...)))' --output=label --config=release && break || sleep 20;
done
./bazelisk run :sign_and_push --config=release -- --keyless $KEYLESS


================================================
FILE: .github/ISSUE_TEMPLATE/actionable-debian-cve.md
================================================
---
name: Actionable debian CVE
about: A CVE from a debian package where a fix is available
title: ''
labels: ''
assignees: ''

---

- [ ] I have read the [SECURITY.md](https://github.com/GoogleContainerTools/distroless/blob/main/SECURITY.md)
- [ ] I understand that this repo tracks debian package releases and cannot fix debian CVEs on its own
- [ ] this CVE shows a fix is available in the appropriate debian version (bookworm) and channel (main, security) *and* it has been more than 48 hours.

Please describe the image you encountered this with and a link to the debian security tracker
https://security-tracker.debian.org/tracker/CVE-XXXX-YYYYY


================================================
FILE: .github/ISSUE_TEMPLATE/actionable-non-debian-cve.md
================================================
---
name: Actionable non debian CVE
about: A CVE is an package imported into distroless that is not from debian
title: ''
labels: ''
assignees: ''

---

**CVE disclosure**
A link to a public CVE disclosure

**Name of image**
The distroless image you are using

**Link to updated package**
A link to an available update for this package


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: There is an issue with how distroless is built
title: ''
labels: ''
assignees: ''

---

**Describe the bug**
A clear and concise description of what the bug is.

**To Reproduce**
Steps to reproduce the behavior:

**Expected behavior**
A clear and concise description of what you expected to happen.

**Console Output**
If applicable, add information from your container run

**Additional context**
Add any other context about the problem here.


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "gomod"
  directory: "/"
  schedule:
    interval: "daily"
- package-ecosystem: "gomod"
  directory: "debian-package-manager/"
  schedule:
    interval: "daily"
- package-ecosystem: "github-actions"
  directory: "/"
  schedule:
    interval: "daily"


================================================
FILE: .github/workflows/buildifier.yaml
================================================
name: Buildifier

on:
  pull_request:
    branches: ["main"]

permissions:
  contents: read

jobs:
  autoformat:
    name: Auto-format and Check
    runs-on: ubuntu-latest

    steps:
      - name: Set up Go
        uses: actions/setup-go@v6
        with:
          go-version: "1.25"

      - name: Check out code
        uses: actions/checkout@v6

      - name: Install Dependencies
        run: |
          go install github.com/bazelbuild/buildtools/buildifier@3.2.0

      - name: Run buildifier
        run: |
          ./knife lint --check


================================================
FILE: .github/workflows/check-ldconfig.yaml
================================================
name: Check ldconfig cache

on:
  pull_request:
    branches: ["main"]
  push:
    branches: ["main"]

permissions:
  contents: read

jobs:
  check-ldconfig:
    name: Check ldconfig cache up to date
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v4

      - name: Check ldconfig caches
        run: bazel test //python3:check_ldconfig_tests


================================================
FILE: .github/workflows/ci.bazelrc
================================================
build --announce_rc
build --repository_cache=~/.cache/bazel-repo
# intentionally disable build cache, it gets too big
# build --disk_cache=~/.cache/bazel

common --curses=no
test  --test_output=errors


================================================
FILE: .github/workflows/ci.yaml
================================================
name: CI

on:
  workflow_dispatch: # Allow manual runs.
  pull_request:
    branches: ["main"]
  push:
    branches: ["main"]

permissions:
  contents: read

jobs:
  lockfile:
    name: Check lockfile up to date
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - name: Check lockfile
        run: bazel mod deps --lockfile_mode=error

  ci-build-test:
    name: CI build and unit test
    runs-on: distroless-ci-large-ubuntu-22.04
    steps:
      - uses: actions/checkout@v6
      - name: Mount bazel caches
        uses: actions/cache@v4
        with:
          path: |
            ~/.cache/bazel-repo
          key: bazel-cache-deps-ci1-${{ github.sha }}
          restore-keys: |
            bazel-cache-deps-ci1-${{ github.sha }}
            bazel-cache-deps-ci1-

      - name: Fetch # this can take a long time if there are a lot of errors
        run: |
          for i in $(seq 10); do
            bazel --bazelrc=$GITHUB_WORKSPACE/.github/workflows/ci.bazelrc fetch //... && break || sleep 180;
          done
      - name: Build All Images
        run: |
          set -e
          targets=$(bazel query 'kind(oci_image, deps(:sign_and_push))')
          bazel --bazelrc=$GITHUB_WORKSPACE/.github/workflows/ci.bazelrc build $targets
      - name: Unit Tests
        run: bazel --bazelrc=$GITHUB_WORKSPACE/.github/workflows/ci.bazelrc test //... --build_tests_only
      - name: Build Examples
        run: bazel --bazelrc=$GITHUB_WORKSPACE/.github/workflows/ci.bazelrc build //examples/...

  ci-images:
    name: CI image tests
    runs-on: distroless-ci-large-ubuntu-22.04
    steps:
      - uses: actions/checkout@v6
      - name: Mount bazel caches
        uses: actions/cache@v4
        with:
          path: |
            ~/.cache/bazel-repo
          key: bazel-cache-deps-ci2-${{ github.sha }}
          restore-keys: |
            bazel-cache-deps-ci2-${{ github.sha }}
            bazel-cache-deps-ci2-

      - name: Fetch
        run: |
          for i in $(seq 20); do
            bazel --bazelrc=$GITHUB_WORKSPACE/.github/workflows/ci.bazelrc cquery 'attr(tags, "amd64", ...)' && break
            sleep 10;
          done

      - name: Image Tests
        run: bazel --bazelrc=$GITHUB_WORKSPACE/.github/workflows/ci.bazelrc test $(bazel query 'attr(tags, "amd64", ...)')


================================================
FILE: .github/workflows/config-diff.yaml
================================================
name: Config Check

on:
  workflow_dispatch:
  pull_request:
    branches: ["main"]

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

permissions:
  pull-requests: write
  
jobs:
  diff:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout PR Branch
        uses: actions/checkout@v6
        with:
          ref: ${{ github.event.pull_request.head.sha }}
          path: pr_branch

      - name: Build :sign_and_push.query for PR
        run: |
          cd pr_branch
          bazel build :sign_and_push.query
          cp bazel-bin/sign_and_push_query ../pr_query_output.txt
          cd ..

      - name: Checkout main Branch
        uses: actions/checkout@v6
        with:
          ref: main
          path: main_branch

      - name: Build :sign_and_push.query for main
        run: |
          cd main_branch
          bazel build :sign_and_push.query
          cp bazel-bin/sign_and_push_query ../main_query_output.txt
          cd ..

      - name: Diff the query outputs
        id: diff
        run: |
          # diff may exit with non-zero
          DIFF_OUTPUT=$(diff -u <(sort main_query_output.txt) <(sort pr_query_output.txt)) || true
          
          if [ "$DIFF_OUTPUT" ]; then
            echo "$DIFF_OUTPUT"
            echo "changed_build<<EOF" >> $GITHUB_OUTPUT
            echo "$DIFF_OUTPUT" >> $GITHUB_OUTPUT
            echo "EOF" >> $GITHUB_OUTPUT
          fi

      - uses: peter-evans/find-comment@v4
        id: fc
        if: ${{ !github.event.pull_request.head.repo.fork }}
        with:
          issue-number: ${{ github.event.pull_request.number }}
          comment-author: "github-actions[bot]"
          body-includes: 🌳 🔧 Config Check

      - name: Report diff
        if: ${{ !github.event.pull_request.head.repo.fork && steps.diff.outputs.changed_build }}
        uses: peter-evans/create-or-update-comment@v5
        with:
          comment-id: ${{ steps.fc.outputs.comment-id }}
          issue-number: ${{ github.event.pull_request.number }}
          body: |
            🌳 🔧 Config Check

            This pull request has modified the root BUILD

            ```diff
            ${{steps.diff.outputs.changed_build}}
            ```
          edit-mode: replace

      - name: Report no diff
        if: ${{ !github.event.pull_request.head.repo.fork && !steps.diff.outputs.changed_build }}
        uses: peter-evans/create-or-update-comment@v5
        with:
          comment-id: ${{ steps.fc.outputs.comment-id }}
          issue-number: ${{ github.event.pull_request.number }}
          body: |
            🌳 🔧 Config Check

            This pull request has not modified the root BUILD
          edit-mode: replace


================================================
FILE: .github/workflows/examples.yaml
================================================
name: Examples

on:
  pull_request:
    branches: [ 'main' ]

permissions:
  contents: read

jobs:
  examples:
    name: Build and run examples
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v6
      - run: ./examples/test.sh


================================================
FILE: .github/workflows/image-check.yaml
================================================
name: Image Check

on:
  workflow_dispatch:
  pull_request:
    branches: ["main"]

concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true

permissions:
  pull-requests: write

jobs:
  diff:
    runs-on: distroless-ci-large-ubuntu-22.04
    steps:
      - uses: actions/checkout@v6
      - name: Set up Go
        uses: actions/setup-go@v6
        with:
          go-version: "1.25"
      - uses: actions/cache@v4
        with:
          path: |
            ~/.cache/bazel-repo
          key: bazel-cache-deps-ci2-${{ github.sha }}
          restore-keys: |
            bazel-cache-deps-ci2-${{ github.sha }}
            bazel-cache-deps-ci2-

      - name: Fetch
        run: |
          for i in $(seq 10); do
            bazel --bazelrc=$GITHUB_WORKSPACE/.github/workflows/ci.bazelrc cquery 'attr(tags, "amd64", ...)' && break
            sleep 10;
          done

      - name: Build all images
        run: bazel build //:sign_and_push

      - name: Install Deps
        run: |
          go install github.com/google/go-containerregistry/cmd/crane@main
          go install github.com/reproducible-containers/diffoci/cmd/diffoci@master
          go install filippo.io/mkcert@master
          sudo curl -fsSL "https://github.com/project-zot/zot/releases/download/v2.0.2-rc2/zot-linux-amd64-minimal" > /usr/local/bin/zot
          sudo chmod +x /usr/local/bin/zot

      - name: Diff All Images
        id: diff
        env:
          HEAD_REF: ${{ github.head_ref }}
          BASE_REF: ${{ github.event.pull_request.base.ref }}
        run: |
          ./private/tools/diff/diff.bash \
            --query-bazel --registry-spawn-https \
            --head-ref "$HEAD_REF" \
            --base-ref "$BASE_REF" \
            --set-github-output-on-diff \
            --skip-image-index \
            --jobs $(($(nproc --all) * 2)) \
            --logs ./verbose.log \
            --report ./report.log

      - uses: actions/upload-artifact@v7.0.0
        id: report
        with:
          name: "Report"
          path: |
            ./verbose.log
            ./report.log

      - name: Write diff to job output for forks
        if: ${{ github.event.pull_request.head.repo.fork && steps.diff.outputs.changed_targets }}
        env:
          CHANGED: ${{ steps.diff.outputs.changed_targets }}
        run: |
          echo "This pull request has modified the following images:"
          echo "$CHANGED"

      - uses: peter-evans/find-comment@v4
        id: fc
        if: ${{ !github.event.pull_request.head.repo.fork }}
        with:
          issue-number: ${{ github.event.pull_request.number }}
          comment-author: "github-actions[bot]"
          body-includes: 🌳 🔄 Image Check

      - name: Report diff
        if: ${{ !github.event.pull_request.head.repo.fork && steps.diff.outputs.changed_targets }}
        uses: peter-evans/create-or-update-comment@v5
        with:
          comment-id: ${{ steps.fc.outputs.comment-id }}
          issue-number: ${{ github.event.pull_request.number }}
          body: |
            🌳 🔄 Image Check

            This pull request has modified the following images:

            ```starlark
            ${{steps.diff.outputs.changed_targets}}
            ```

            You can check the details in the report [here](${{steps.report.outputs.artifact-url}})
          edit-mode: replace

      - name: Report no diff
        if: ${{ !github.event.pull_request.head.repo.fork && !steps.diff.outputs.changed_targets }}
        uses: peter-evans/create-or-update-comment@v5
        with:
          comment-id: ${{ steps.fc.outputs.comment-id }}
          issue-number: ${{ github.event.pull_request.number }}
          body: |
            🌳 🔄 Image Check
            This pull request doesn't make any changes to the images. 👍
            You can check the details in the report [here](${{steps.report.outputs.artifact-url}})
          edit-mode: replace


================================================
FILE: .github/workflows/scorecards-analysis.yml
================================================
name: Scorecards supply-chain security
on: 
  # Only the default branch is supported.
  branch_protection_rule:
  schedule:
    - cron: '38 10 * * 1'
  push:
    branches: [ main ]

# Declare default permissions as read only.
permissions: read-all

jobs:
  analysis:
    name: Scorecards analysis
    runs-on: ubuntu-latest
    permissions:
      # Needed to upload the results to code-scanning dashboard.
      security-events: write
      id-token: write
    
    steps:
      - name: "Checkout code"
        uses: actions/checkout@v6
        with:
          persist-credentials: false

      - name: "Run analysis"
        uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
        with:
          results_file: results.sarif
          results_format: sarif
          # Read-only PAT token.
          repo_token: ${{ secrets.SCORECARD_TOKEN }}
          # Publish the results to enable scorecard badges. For more details, see
          # https://github.com/ossf/scorecard-action#publishing-results.
          publish_results: true

      # Upload the results as artifacts (optional).
      - name: "Upload artifact"
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
        with:
          name: SARIF file
          path: results.sarif
          retention-days: 5
      
      # Upload the results to GitHub's code scanning dashboard.
      - name: "Upload to code-scanning"
        uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v4.32.6
        with:
          sarif_file: results.sarif


================================================
FILE: .github/workflows/update-deb-package-non-snapshots.yml
================================================
name: update-non-snapshots
on:
  # will send emails to last editor of this cron syntax (distroless-bot)
  schedule:
    - cron: "35 20 * * *"
  # allow this workflow to be manually run
  workflow_dispatch:

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write

    steps:
      - uses: actions/checkout@v6

      - name: Update non-snapshots
        run: ./knife update-non-snapshots

      - name: Check for changes
        run: |
          git status
          if [[ $(git status --porcelain) ]]; then 
            echo "DISTROLESS_DIFF=true" >> "$GITHUB_ENV"
          else
            echo "No changes detected"
            exit 0
          fi

      - name: Run update lockfile
        if: env.DISTROLESS_DIFF
        run: bazel mod deps --lockfile_mode=update

      - name: Create commits
        if: env.DISTROLESS_DIFF
        id: create-commits
        run: |
          git checkout -b update-non-snapshots

          # Set identity.
          git config --global user.email "distroless-bot@google.com"
          git config --global user.name "Distroless Bot"

          # Commit changes
          git add .
          git commit -s -m "Bumping non-snapshot packages to latest stable versions"
          git push --force origin HEAD

      - name: Create Pull Request
        if: env.DISTROLESS_DIFF
        env:
          GH_TOKEN: ${{ secrets.ACTIONS_TOKEN }}
        run: |
          BODY_FILE=$(mktemp)
          printf "Bumping non-snapshot packages to latest stable version\n" >> $BODY_FILE
          if ! OUTPUT=$(gh pr create -B main -H update-non-snapshots -t "Bumping non-snapshot packages to latest stable versions" --body-file "$BODY_FILE" 2>&1) ; then
            echo $OUTPUT
            if [[ "${OUTPUT}" =~ "already exists" ]]; then
              echo "PR already exists and it was updated. Ending successfully";
              exit 0;
            else
              exit 1;
            fi
          fi


================================================
FILE: .github/workflows/update-deb-package-snapshots.yml
================================================
name: update-snapshots
on:
  # will send emails to last editor of this cron syntax (distroless-bot)
  schedule:
    - cron: "35 20 * * *"
  # allow this workflow to be manually run
  workflow_dispatch:

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write

    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-go@v6
        with:
          go-version: "1.25"

      - name: Update snapshots
        run: ./knife github-update-snapshots

      - name: Set up QEMU
        if: env.DISTROLESS_DIFF
        uses: docker/setup-qemu-action@v4

      - name: Update ldconfig
        if: env.DISTROLESS_DIFF
        run: bazel run //python3:update_ldconfig

      - name: Run update sboms script
        if: env.DISTROLESS_DIFF
        run: |
          for i in $(seq 5); do
            bazel --bazelrc=$GITHUB_WORKSPACE/.github/workflows/ci.bazelrc fetch //private/pkg/test/oci_image:test_sboms && break || sleep 20;
          done
          bazel run @//private/pkg/test/oci_image:test_sboms

      - name: Run update lockfile
        if: env.DISTROLESS_DIFF
        run: bazel mod deps --lockfile_mode=update

      - name: Create commits
        if: env.DISTROLESS_DIFF
        id: create-commits
        run: |
          git checkout -b update-snapshots

          # Set identity.
          git config --global user.email "distroless-bot@google.com"
          git config --global user.name "Distroless Bot"

          # Commit changes
          git add .
          git commit -s -m "Bumping packages to latest stable versions"
          git push --force origin HEAD

      - name: Create Pull Request
        if: env.DISTROLESS_DIFF
        env:
          GH_TOKEN: ${{ secrets.ACTIONS_TOKEN }}
        run: |
          BODY_FILE=$(mktemp)
          printf "Bumping packages to latest stable version\n\`\`\`diff\n$DISTROLESS_DIFF\n\`\`\`\n" >> $BODY_FILE
          if ! OUTPUT=$(gh pr create -B main -H update-snapshots -t "Bumping packages to latest stable versions" --body-file "$BODY_FILE" 2>&1) ; then
            echo $OUTPUT
            if [[ "${OUTPUT}" =~ "already exists" ]]; then
              echo "PR already exists and it was updated. Ending successfully";
              exit 0;
            else
              exit 1;
            fi
          fi


================================================
FILE: .github/workflows/update-node-archives.yml
================================================
name: update-node-packages
on:
  # will send emails to last editor of this cron syntax (distroless-bot)
  schedule:
    - cron: "30 20 * * *"
  # allow this workflow to be manually run
  workflow_dispatch:

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write

    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6

      - name: Update node archives
        run: ./knife update-node-archives

      - name: Check for changes
        run: |
          git status
          if [[ $(git status --porcelain) ]]; then
            echo "DISTROLESS_DIFF=true" >> "$GITHUB_ENV"
          else
            echo "No changes detected"
            exit 0
          fi

      - name: Run update lockfile
        if: env.DISTROLESS_DIFF
        run: bazel mod deps --lockfile_mode=update

      - name: Create commits
        if: env.DISTROLESS_DIFF
        run: |
          git checkout -b update-node-archives

          # Set identity.
          git config --global user.email "distroless-bot@google.com"
          git config --global user.name "Distroless Bot"

          # Commit changes
          git add .
          git commit -s -m "Bumping node archives to latest stable versions"
          git push --force origin HEAD

      - name: Create Pull Request
        if: env.DISTROLESS_DIFF
        env:
          GH_TOKEN: ${{ secrets.ACTIONS_TOKEN }}
        run: |
          BODY_FILE=$(mktemp)
          if ! OUTPUT=$(gh pr create -B main -H update-node-archives --fill 2>&1) ; then
            echo $OUTPUT
            if [[ "${OUTPUT}" =~ "already exists" ]]; then
              echo "PR already exists and it was updated. Ending successfully";
              exit 0;
            else
              exit 1;
            fi
          fi


================================================
FILE: .gitignore
================================================
# Ignore backup files.
*~
# Ignore Vim swap files.
.*.swp
# Ignore files generated by IDEs.
/.classpath
/.factorypath
.idea/
*.iml
/.project
/.settings
/bazel.iml
# Ignore all bazel-* symlinks. There is no full list since this can change
# based on the name of the directory bazel is cloned into.
/bazel-*
# Ignore outputs generated during Bazel bootstrapping.
/output/
# ignore user bazelrc
.bazelrc.user
*.log

================================================
FILE: .pre-commit-config.yaml
================================================
# See CONTRIBUTING.md for instructions.
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks

# Commitizen runs in commit-msg stage
# but we don't want to run the other hooks on commit messages
default_stages: [commit]

# Use a slightly older version of node by default
# as the default uses a very new version of GLIBC
default_language_version:
  node: 16.18.0

repos:
  # Check formatting and lint for starlark code
  - repo: https://github.com/keith/pre-commit-buildifier
    rev: 6.1.0.1
    hooks:
      - id: buildifier
      - id: buildifier-lint
  # Enforce that commit messages allow for later changelog generation
  - repo: https://github.com/commitizen-tools/commitizen
    rev: v2.18.0
    hooks:
      # Requires that commitizen is already installed
      - id: commitizen
        stages: [commit-msg]
  - repo: https://github.com/pre-commit/mirrors-prettier
    rev: "v2.4.0"
    hooks:
      - id: prettier


================================================
FILE: .prettierignore
================================================
private/pkg/test/oci_image/*

================================================
FILE: BUILD
================================================
load("//base:config.bzl", "BASE_ARCHITECTURES", "BASE_DISTROS")
load("//cc:config.bzl", "CC_ARCHITECTURES", "CC_DISTROS")
load("//java:config.bzl", "JAVA_ARCHITECTURES", "JAVA_DISTROS", "JAVA_MAJOR_VERSIONS")
load("//nodejs:config.bzl", "NODEJS_ARCHITECTURES", "NODEJS_DISTROS", "NODEJS_MAJOR_VERSIONS")
load("//private/oci:defs.bzl", "sign_and_push_all")
load("//private/tools/lifecycle:defs.bzl", "attach_lifecycle_tags")
load("//python3:config.bzl", "PYTHON_ARCHITECTURES", "PYTHON_DISTROS")
load("//static:config.bzl", "STATIC_ARCHITECTURES", "STATIC_DISTROS")

package(default_visibility = ["//visibility:public"])

DEFAULT_DISTRO = "debian13"

VARIANTS = [
    ("latest", "", "root"),
    ("nonroot", "", "nonroot"),
    ("debug", "_debug", "root"),
    ("debug-nonroot", "_debug", "nonroot"),
]

###############
# STATIC      #
###############
STATIC = {
    "{REGISTRY}/{PROJECT_ID}/static:" + tag_base + "-" + arch: "//static:static" + debug_mode + "_" + user + "_" + arch + "_" + DEFAULT_DISTRO
    for arch in STATIC_ARCHITECTURES[DEFAULT_DISTRO]
    for (tag_base, debug_mode, user) in VARIANTS
}

# oci_image_index
STATIC |= {
    "{REGISTRY}/{PROJECT_ID}/static:" + tag_base: "//static:static" + debug_mode + "_" + user + "_" + DEFAULT_DISTRO
    for (tag_base, debug_mode, user) in VARIANTS
}

STATIC |= {
    "{REGISTRY}/{PROJECT_ID}/static-" + distro + ":" + tag_base + "-" + arch: "//static:static" + debug_mode + "_" + user + "_" + arch + "_" + distro
    for (tag_base, debug_mode, user) in VARIANTS
    for distro in STATIC_DISTROS
    for arch in STATIC_ARCHITECTURES[distro]
}

# oci_image_index
STATIC |= {
    "{REGISTRY}/{PROJECT_ID}/static-" + distro + ":" + tag_base: "//static:static" + debug_mode + "_" + user + "_" + distro
    for (tag_base, debug_mode, user) in VARIANTS
    for distro in STATIC_DISTROS
}

###############
# BASE        #
###############
BASE = {
    "{REGISTRY}/{PROJECT_ID}/base:" + tag_base + "-" + arch: "//base:base" + debug_mode + "_" + user + "_" + arch + "_" + DEFAULT_DISTRO
    for arch in BASE_ARCHITECTURES[DEFAULT_DISTRO]
    for (tag_base, debug_mode, user) in VARIANTS
}

# oci_image_index
BASE |= {
    "{REGISTRY}/{PROJECT_ID}/base:" + tag_base: "//base:base" + debug_mode + "_" + user + "_" + DEFAULT_DISTRO
    for (tag_base, debug_mode, user) in VARIANTS
}

BASE |= {
    "{REGISTRY}/{PROJECT_ID}/base-" + distro + ":" + tag_base + "-" + arch: "//base:base" + debug_mode + "_" + user + "_" + arch + "_" + distro
    for distro in BASE_DISTROS
    for arch in BASE_ARCHITECTURES[distro]
    for (tag_base, debug_mode, user) in VARIANTS
}

# oci_image_index
BASE |= {
    "{REGISTRY}/{PROJECT_ID}/base-" + distro + ":" + tag_base: "//base:base" + debug_mode + "_" + user + "_" + distro
    for (tag_base, debug_mode, user) in VARIANTS
    for distro in BASE_DISTROS
}

###############
# BASE_NOSSL  #
###############
BASE_NOSSL = {
    "{REGISTRY}/{PROJECT_ID}/base-nossl:" + tag_base + "-" + arch: "//base:base_nossl" + debug_mode + "_" + user + "_" + arch + "_" + DEFAULT_DISTRO
    for arch in BASE_ARCHITECTURES[DEFAULT_DISTRO]
    for (tag_base, debug_mode, user) in VARIANTS
}

# oci_image_index
BASE_NOSSL |= {
    "{REGISTRY}/{PROJECT_ID}/base-nossl:" + tag_base: "//base:base_nossl" + debug_mode + "_" + user + "_" + DEFAULT_DISTRO
    for (tag_base, debug_mode, user) in VARIANTS
}

BASE_NOSSL |= {
    "{REGISTRY}/{PROJECT_ID}/base-nossl-" + distro + ":" + tag_base + "-" + arch: "//base:base_nossl" + debug_mode + "_" + user + "_" + arch + "_" + distro
    for (tag_base, debug_mode, user) in VARIANTS
    for distro in BASE_DISTROS
    for arch in BASE_ARCHITECTURES[distro]
}

# oci_image_index
BASE_NOSSL |= {
    "{REGISTRY}/{PROJECT_ID}/base-nossl-" + distro + ":" + tag_base: "//base:base_nossl" + debug_mode + "_" + user + "_" + distro
    for (tag_base, debug_mode, user) in VARIANTS
    for distro in BASE_DISTROS
}

###############
# CC          #
###############
CC = {
    "{REGISTRY}/{PROJECT_ID}/cc:" + tag_base + "-" + arch: "//cc:cc" + debug_mode + "_" + user + "_" + arch + "_" + DEFAULT_DISTRO
    for arch in CC_ARCHITECTURES[DEFAULT_DISTRO]
    for (tag_base, debug_mode, user) in VARIANTS
}

# oci_image_index
CC |= {
    "{REGISTRY}/{PROJECT_ID}/cc:" + tag_base: "//cc:cc" + debug_mode + "_" + user + "_" + DEFAULT_DISTRO
    for (tag_base, debug_mode, user) in VARIANTS
}

CC |= {
    "{REGISTRY}/{PROJECT_ID}/cc-" + distro + ":" + tag_base + "-" + arch: "//cc:cc" + debug_mode + "_" + user + "_" + arch + "_" + distro
    for (tag_base, debug_mode, user) in VARIANTS
    for distro in CC_DISTROS
    for arch in CC_ARCHITECTURES[distro]
}

# oci_image_index
CC |= {
    "{REGISTRY}/{PROJECT_ID}/cc-" + distro + ":" + tag_base: "//cc:cc" + debug_mode + "_" + user + "_" + distro
    for (tag_base, debug_mode, user) in VARIANTS
    for distro in CC_DISTROS
}

###############
# PYTHON 3    #
###############
PYTHON3 = {
    "{REGISTRY}/{PROJECT_ID}/python3:" + tag_base + "-" + arch: "//python3:python3" + debug_mode + "_" + user + "_" + arch + "_" + DEFAULT_DISTRO
    for arch in PYTHON_ARCHITECTURES[DEFAULT_DISTRO]
    for (tag_base, debug_mode, user) in VARIANTS
}

# oci_image_index
PYTHON3 |= {
    "{REGISTRY}/{PROJECT_ID}/python3:" + tag_base: "//python3:python3" + debug_mode + "_" + user + "_" + DEFAULT_DISTRO
    for (tag_base, debug_mode, user) in VARIANTS
}

PYTHON3 |= {
    "{REGISTRY}/{PROJECT_ID}/python3-" + distro + ":" + tag_base + "-" + arch: "//python3:python3" + debug_mode + "_" + user + "_" + arch + "_" + distro
    for distro in PYTHON_DISTROS
    for arch in PYTHON_ARCHITECTURES[distro]
    for (tag_base, debug_mode, user) in VARIANTS
}

# oci_image_index
PYTHON3 |= {
    "{REGISTRY}/{PROJECT_ID}/python3-" + distro + ":" + tag_base: "//python3:python3" + debug_mode + "_" + user + "_" + distro
    for distro in PYTHON_DISTROS
    for (tag_base, debug_mode, user) in VARIANTS
}

###############
# NODEJS      #
###############
NODEJS = {
    "{REGISTRY}/{PROJECT_ID}/nodejs" + version + "-" + distro + ":" + tag_base + "-" + arch: "//nodejs:nodejs" + version + debug_mode + "_" + user + "_" + arch + "_" + distro
    for version in NODEJS_MAJOR_VERSIONS
    for distro in NODEJS_DISTROS
    for arch in NODEJS_ARCHITECTURES[distro][version]
    for (tag_base, debug_mode, user) in VARIANTS
}

# oci_image_index
NODEJS |= {
    "{REGISTRY}/{PROJECT_ID}/nodejs" + version + "-" + distro + ":" + tag_base: "//nodejs:nodejs" + version + debug_mode + "_" + user + "_" + distro
    for version in NODEJS_MAJOR_VERSIONS
    for distro in NODEJS_DISTROS
    for (tag_base, debug_mode, user) in VARIANTS
}

NODEJS |= {
    "{REGISTRY}/{PROJECT_ID}/nodejs" + version + ":" + tag_base + "-" + arch: "//nodejs:nodejs" + version + debug_mode + "_" + user + "_" + arch + "_" + DEFAULT_DISTRO
    for version in NODEJS_MAJOR_VERSIONS
    for arch in NODEJS_ARCHITECTURES[DEFAULT_DISTRO][version]
    for (tag_base, debug_mode, user) in VARIANTS
}

# oci_image_index
NODEJS |= {
    "{REGISTRY}/{PROJECT_ID}/nodejs" + version + ":" + tag_base: "//nodejs:nodejs" + version + debug_mode + "_" + user + "_" + DEFAULT_DISTRO
    for version in NODEJS_MAJOR_VERSIONS
    for (tag_base, debug_mode, user) in VARIANTS
}

###############
# JAVA_BASE   #
###############
JAVA_BASE = {
    "{REGISTRY}/{PROJECT_ID}/java-base:" + tag_base + "-" + arch: "//java:java_base" + debug_mode + "_" + user + "_" + arch + "_" + DEFAULT_DISTRO
    for arch in JAVA_ARCHITECTURES[DEFAULT_DISTRO]
    for (tag_base, debug_mode, user) in VARIANTS
}

JAVA_BASE |= {
    "{REGISTRY}/{PROJECT_ID}/java-base:" + tag_base: "//java:java_base" + debug_mode + "_" + user + "_" + DEFAULT_DISTRO
    for (tag_base, debug_mode, user) in VARIANTS
}

JAVA_BASE |= {
    "{REGISTRY}/{PROJECT_ID}/java-base-" + distro + ":" + tag_base + "-" + arch: "//java:java_base" + debug_mode + "_" + user + "_" + arch + "_" + distro
    for distro in JAVA_DISTROS
    for arch in JAVA_ARCHITECTURES[distro]
    for (tag_base, debug_mode, user) in VARIANTS
}

JAVA_BASE |= {
    "{REGISTRY}/{PROJECT_ID}/java-base-" + distro + ":" + tag_base: "//java:java_base" + debug_mode + "_" + user + "_" + distro
    for (tag_base, debug_mode, user) in VARIANTS
    for distro in JAVA_DISTROS
}

###############
# JAVA        #
###############
JAVA = {
    "{REGISTRY}/{PROJECT_ID}/java" + version + ":" + tag_base + "-" + arch: "//java:java" + version + debug_mode + "_" + user + "_" + arch + "_" + DEFAULT_DISTRO
    for version in JAVA_MAJOR_VERSIONS[DEFAULT_DISTRO]
    for arch in JAVA_ARCHITECTURES[DEFAULT_DISTRO]
    for (tag_base, debug_mode, user) in VARIANTS
}

# oci_image_index
JAVA |= {
    "{REGISTRY}/{PROJECT_ID}/java" + version + ":" + tag_base: "//java:java" + version + debug_mode + "_" + user + "_" + DEFAULT_DISTRO
    for version in JAVA_MAJOR_VERSIONS[DEFAULT_DISTRO]
    for (tag_base, debug_mode, user) in VARIANTS
}

JAVA |= {
    "{REGISTRY}/{PROJECT_ID}/java" + version + "-" + distro + ":" + tag_base + "-" + arch: "//java:java" + version + debug_mode + "_" + user + "_" + arch + "_" + distro
    for distro in JAVA_DISTROS
    for version in JAVA_MAJOR_VERSIONS[distro]
    for arch in JAVA_ARCHITECTURES[distro]
    for (tag_base, debug_mode, user) in VARIANTS
}

# oci_image_index
JAVA |= {
    "{REGISTRY}/{PROJECT_ID}/java" + version + "-" + distro + ":" + tag_base: "//java:java" + version + debug_mode + "_" + user + "_" + distro
    for (tag_base, debug_mode, user) in VARIANTS
    for distro in JAVA_DISTROS
    for version in JAVA_MAJOR_VERSIONS[distro]
}

ALL = {}

ALL |= STATIC

ALL |= BASE

ALL |= BASE_NOSSL

ALL |= CC

ALL |= PYTHON3

ALL |= NODEJS

ALL |= JAVA_BASE

ALL |= JAVA

# create additional tags by appending COMMIT_SHA to all tags
# remove "latest" if they contain it (this is brittle if we make funky changes):
# - image:latest -> image:{COMMIT_SHA}
# - image:latest-xyz -> image:xyz-{COMMIT_SHA}
COMMIT_SUFFIXED_TAGS = {
    (image_ref.replace("latest", "") + "-{COMMIT_SHA}").replace(":-", ":"): build_target
    for (image_ref, build_target) in ALL.items()
}

ALL |= COMMIT_SUFFIXED_TAGS

sign_and_push_all(
    name = "sign_and_push",
    images = ALL,
)

attach_lifecycle_tags(
    name = "attach_lifecycle_tags",
    images = ALL,
)


================================================
FILE: CONTRIBUTING.md
================================================
# How to become a contributor and submit your own code

## Contributor License Agreements

We'd love to accept your patches! Before we can take them, we have to jump a couple of legal hurdles.

Please fill out either the individual or corporate [Contributor License Agreement (CLA)](https://cla.developers.google.com/about).

- If you are an individual writing original source code and you're sure you own the intellectual property, then you'll need to sign an [individual CLA](https://cla.developers.google.com/about/google-individual).
- If you work for a company that wants to allow you to contribute your work, then you'll need to sign a [corporate CLA](https://cla.developers.google.com/about/google-corporate).

Follow either of the two links above to access the appropriate CLA and instructions for how to sign and return it. Once we receive it, we'll be able to accept your pull requests.

## How to Build and Test

1. `bazel build //...` to build the whole project or ex:`bazel build //base:static_root_amd64_debian17` for a single image

2. For running tests, check `./knife test`. (`bazel test //...` will NOT run all tests, as many tests are marked "manual".)

3. For building and loading images to your local Docker engine, you need to add a new rule for that image to the BUILD:

```starlark
load("@rules_oci//oci:defs.bzl", "oci_load")

oci_load(
  name = "local_build",
  image = "//base:static_root_amd64_debian17",
  repo_tags = [],
)
```

then run the following command to load into the daemon

```shell
bazel run //:local_build
```

## Adding or removing Debian packages

Whenever a change made to `common/*.yaml` manifests, the locking step has to be performed to regenerate lock files.

This can be done by running; `./knife lock`

## Code style

For styling Bazel files, install and run `buildifier` with:

```shell
# Install buildifier version 3.2.0
go install github.com/bazelbuild/buildtools/buildifier@latest

# This will automatically fix files.
buildifier -mode=fix $(find . -name 'BUILD*' -o -name 'WORKSPACE*' -o -name '*.bzl' -type f)
```

For styling Python files, [install](https://www.pylint.org/#install) and run `pylint` with:

```shell
# Install pylint
sudo pip install pylint
# Or
sudo apt-get install pylint
# Or on macos
brew install pylint

# Identify python style issues.
find . -name "*.py" | xargs pylint --disable=R,C
```

## Contributing a Patch

1. Submit an issue describing your proposed change to the repo in question.
1. The repo owner will respond to your issue promptly.
1. If your proposed change is accepted, and you haven't already done so, sign a Contributor License Agreement (see details above).
1. Fork the desired repo, develop and test your code changes.
1. Submit a pull request.


================================================
FILE: LICENSE
================================================

                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   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.


================================================
FILE: MODULE.bazel
================================================
"distroless"

module(name = "distroless")

bazel_dep(name = "bazel_skylib", version = "1.8.2")
bazel_dep(name = "aspect_bazel_lib", version = "2.21.1")
bazel_dep(name = "rules_pkg", version = "1.1.0")
bazel_dep(name = "platforms", version = "1.0.0")
bazel_dep(name = "rules_go", version = "0.57.0")
bazel_dep(name = "gazelle", version = "0.38.0")
bazel_dep(name = "rules_rust", version = "0.63.0")
bazel_dep(name = "container_structure_test", version = "1.19.1")
bazel_dep(name = "rules_oci", version = "2.2.7")
bazel_dep(name = "rules_distroless", version = "0.6.2")
bazel_dep(name = "rules_python", version = "1.5.3")
bazel_dep(name = "rules_cc", version = "0.2.4")

### OCI ###
# Note: rules_oci registers toolchains in its MODULE.bazel
# We import the repos to use crane in sign_and_push rule
oci = use_extension("@rules_oci//oci:extensions.bzl", "oci")
use_repo(oci, "oci_crane_toolchains")

### PYTHON ###
python = use_extension("@rules_python//python/extensions:python.bzl", "python")
python.toolchain(
    ignore_root_user_error = True,
    python_version = "3.11",
)

### GO ####
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.module(
    path = "github.com/spdx/tools-golang",
    sum = "h1:9B623Cfs+mclYK6dsae7gLSwuIBHvlgmEup87qpqsAQ=",
    version = "v0.3.1-0.20230104082527-d6f58551be3f",
)
use_repo(go_deps, "com_github_spdx_tools_golang")

### BUSYBOX ###
busybox = use_extension("//private/extensions:busybox.bzl", "busybox")
busybox.archive()
use_repo(busybox, "busybox_amd64", "busybox_arm", "busybox_arm64", "busybox_ppc64le", "busybox_riscv64", "busybox_s390x")

### JAVA ###

### NODE ###
node = use_extension("//private/extensions:node.bzl", "node")
node.archive()
use_repo(node, "nodejs20_amd64", "nodejs20_arm", "nodejs20_arm64", "nodejs20_ppc64le", "nodejs20_s390x", "nodejs22_amd64", "nodejs22_arm", "nodejs22_arm64", "nodejs22_ppc64le", "nodejs22_s390x", "nodejs24_amd64", "nodejs24_arm64", "nodejs24_ppc64le", "nodejs24_s390x")

### DEBIAN ###
include("//private/repos/deb:deb.MODULE.bazel")


================================================
FILE: PACKAGE_METADATA.md
================================================
# Package Metadata

## `dpkg` Metadata Structure Standard

### Overview

This document defines the standard structure for `dpkg` metadata within Google Distroless Debian-based images. The goal is to provide a clear specification for tool authors and users, enabling consistent and accurate results for vulnerability (CVE) scanning and package analysis.

### Directory Structure

Distroless Debian images differ from traditional Debian images in their handling of `dpkg` metadata. The relevant structure is as follows:

```
/var/lib/dpkg/
    └── status.d/
                ├── <package>
                └── <package>.md5sums
```

### Details

- **`/var/lib/dpkg/status.d/`**  
    - Contains one file per installed package.
    - Each file is named after the package (e.g., `libc6`, `libssl1.1`).

- **`/var/lib/dpkg/status.d/<package>`**  
    - Contains package metadata, equivalent to the output of:  
        ```
        dpkg-deb --field <package>.deb > /var/lib/dpkg/status.d/<package>
        ```

- **`/var/lib/dpkg/status.d/<package>.md5sums`**  
    - Contains file checksums, equivalent to the output of:  
        ```
        dpkg-deb --control <package>.deb CONTROL
        cp CONTROL/md5sums /var/lib/dpkg/status.d/<package>.md5sums
        rm -rf CONTROL
        ```

### Omitted Files

Distroless images intentionally omit several files found in standard Debian images:

- `/var/lib/dpkg/status`  
    - **Not present.** Replaced by the `status.d` directory.

- `/var/lib/dpkg/info/<package>.list`  
    - **Not present.** File lists can be inferred from the `.md5sums` files.

- `/var/lib/dpkg/info/<package>.md5sums`  
    - **Not present.** Checksums are stored in `status.d/<package>.md5sums`.

- Other `/var/lib/dpkg/info/<package>.*` files  
    - **Not present.** Not required for CVE scanning.

### Rationale

- **CVE Scanning Compatibility:**  
    The provided metadata is sufficient for most vulnerability scanners to identify installed packages and their versions.

- **Avoiding Conflicts:**  
    Using the `status.d` directory exclusively prevents confusion or potential issues that could arise if files conflicted with default `dpkg` folders. This approach ensures compatibility, especially if `dpkg` is used directly within the image.

### Guidance for Tool Authors

- Scan `/var/lib/dpkg/status.d/` for installed package metadata.
- Use `<package>` files for package details.
- Use `<package>.md5sums` for file checksums and file lists.
- Do not expect `/var/lib/dpkg/status` or `/var/lib/dpkg/info/` files.

## Non-debian packages
TODO


================================================
FILE: README.md
================================================
# "Distroless" Container Images.

[![CI Build Status](https://github.com/GoogleContainerTools/distroless/actions/workflows/ci.yaml/badge.svg)](https://github.com/GoogleContainerTools/distroless/actions/workflows/ci.yaml)

"Distroless" images contain only your application and its runtime dependencies.
They do not contain package managers, shells or any other programs you would expect to find in a standard Linux distribution.

For more information, see this [talk](https://swampup2017.sched.com/event/A6CW/distroless-docker-containerizing-apps-not-vms?iframe=no&w=100%&sidebar=yes&bg=no) ([video](https://www.youtube.com/watch?v=lviLZFciDv4)).

## Why should I use distroless images?

Restricting what's in your runtime container to precisely what's necessary for your app is a best practice employed by Google
and other tech giants that have used containers in production for many years.
It improves the signal to noise of scanners (e.g. CVE) and reduces the burden of establishing provenance to just what you need.

Distroless images are _very small_.
The smallest distroless image, `gcr.io/distroless/static-debian12`, is around 2 MiB.
That's about 50% of the size of `alpine` (~5 MiB), and less than 2% of the size of `debian` (124 MiB).

## How do I use distroless images?

These images are built using [bazel](https://bazel.build), but they can also be used through other Docker image build tooling.

### What images are available?

The following images are currently published and updated by the distroless project (see [SUPPORT_POLICY.md](SUPPORT_POLICY.md) for support timelines)

These images refer to image indexes with references to all supported architectures. Architecture specific images can be directly referenced using an additional architecture suffix on the tag, like `gcr.io/distroless/static-debian12:latest-amd64`

Any other tags are considered deprecated and are no longer updated

#### Debian 12

| Image                                 | Tags                                  | Architecture Suffixes             |
| ------------------------------------- | ------------------------------------- | --------------------------------- |
| gcr.io/distroless/static-debian12     | latest, nonroot, debug, debug-nonroot | amd64, arm64, arm, s390x, ppc64le |
| gcr.io/distroless/base-debian12       | latest, nonroot, debug, debug-nonroot | amd64, arm64, arm, s390x, ppc64le |
| gcr.io/distroless/base-nossl-debian12 | latest, nonroot, debug, debug-nonroot | amd64, arm64, arm, s390x, ppc64le |
| gcr.io/distroless/cc-debian12         | latest, nonroot, debug, debug-nonroot | amd64, arm64, arm, s390x, ppc64le |
| gcr.io/distroless/python3-debian12    | latest, nonroot, debug, debug-nonroot | amd64, arm64                      |

#### Debian 13

Debian 13 distroless images use the debian [UsrMerge](https://wiki.debian.org/UsrMerge) scheme. If you use `rules_distroless` to add packages to an image, set `mergedusr = True` in [`apt.install`](https://registry.bazel.build/docs/rules_distroless#apt_install).

| Image                                 | Tags                                  | Architecture Suffixes                      |
| ------------------------------------- | ------------------------------------- | ------------------------------------------ |
| gcr.io/distroless/static-debian13     | latest, nonroot, debug, debug-nonroot | amd64, arm64, arm, s390x, ppc64le, riscv64 |
| gcr.io/distroless/base-debian13       | latest, nonroot, debug, debug-nonroot | amd64, arm64, arm, s390x, ppc64le, riscv64 |
| gcr.io/distroless/base-nossl-debian13 | latest, nonroot, debug, debug-nonroot | amd64, arm64, arm, s390x, ppc64le, riscv64 |
| gcr.io/distroless/cc-debian13         | latest, nonroot, debug, debug-nonroot | amd64, arm64, arm, s390x, ppc64le, riscv64 |
| gcr.io/distroless/java-base-debian13  | latest, nonroot, debug, debug-nonroot | amd64, arm64, s390x, ppc64le, riscv64      |
| gcr.io/distroless/java17-debian13     | latest, nonroot, debug, debug-nonroot | amd64, arm64, s390x, ppc64le, riscv64      |
| gcr.io/distroless/java21-debian13     | latest, nonroot, debug, debug-nonroot | amd64, arm64, s390x, ppc64le, riscv64      |
| gcr.io/distroless/java25-debian13     | latest, nonroot, debug, debug-nonroot | amd64, arm64, s390x, ppc64le, riscv64      |
| gcr.io/distroless/nodejs20-debian13   | latest, nonroot, debug, debug-nonroot | amd64, arm64, arm, s390x, ppc64le          |
| gcr.io/distroless/nodejs22-debian13   | latest, nonroot, debug, debug-nonroot | amd64, arm64, arm, s390x, ppc64le          |
| gcr.io/distroless/nodejs24-debian13   | latest, nonroot, debug, debug-nonroot | amd64, arm64, s390x, ppc64le               |
| gcr.io/distroless/python3-debian13    | latest, nonroot, debug, debug-nonroot | amd64, arm64                               |

## Why is distroless still using `gcr.io` instead of `pkg.dev`?

Distroless's serving infrastructure has moved to artifact registry but we still use the `gcr.io` domain. Users will get the benefits of the newer infrastructure without changing their builds.

## How do I verify distroless images?

All distroless images are signed by [cosign](https://github.com/sigstore/cosign) with ephemeral keys (keyless). We recommend verifying any distroless image you use before building your image. You can verify the keyless signature of any distroless image with:

```sh
cosign verify $IMAGE_NAME --certificate-oidc-issuer https://accounts.google.com --certificate-identity keyless@distroless.iam.gserviceaccount.com
```

### Entrypoints

Note that distroless images by default do not contain a shell.
That means the Dockerfile `ENTRYPOINT` command, when defined, must be specified in `vector` form, to avoid the container runtime prefixing with a shell.

This works:

```dockerfile
ENTRYPOINT ["myapp"]
```

But this does not work:

```dockerfile
ENTRYPOINT "myapp"
```

For the same reasons, if the entrypoint is set to the empty vector, the CMD command should be specified in `vector` form (see examples below).
Note that by default static, base and cc images have the empty vector entrypoint. Images with an included language runtime have a language specific default (see: [java](java/README.md#usage), [nodejs](nodejs/README.md#usage), [python3](python3/README.md#usage)).

### Docker

Docker multi-stage builds make using distroless images easy.
Follow these steps to get started:

- Pick the right base image for your application stack.
- Write a multi-stage docker file.
  Note: This requires Docker 17.05 or higher.

  The basic idea is that you'll have one stage to build your application artifacts, and insert them into your runtime distroless image.
  If you'd like to learn more, please see the documentation on [multi-stage builds](https://docs.docker.com/engine/userguide/eng-image/multistage-build/).

#### Examples with Docker

Here's a quick example for go:

```dockerfile
# Start by building the application.
FROM golang:1.18 as build

WORKDIR /go/src/app
COPY . .

RUN go mod download
RUN CGO_ENABLED=0 go build -o /go/bin/app

# Now copy it into our base image.
FROM gcr.io/distroless/static-debian12
COPY --from=build /go/bin/app /
CMD ["/app"]
```

You can find other examples here:

- [Java](examples/java/Dockerfile)
- [Python 3](examples/python3/Dockerfile)
- [Go](examples/go/Dockerfile)
- [Node.js](examples/nodejs/Dockerfile)
- [Rust](examples/rust/Dockerfile)

To run any example, go to the directory for the language and run:

```sh
docker build -t myapp .
docker run -t myapp
```

To run the [Node.js Express example app](examples/nodejs/node-express) and expose the container's ports:

```sh
npm install # Install express and its transitive dependencies
docker build -t myexpressapp . # Normal build command
docker run -p 3000:3000 -t myexpressapp
```

This should expose the Express application to your `localhost:3000`

### Bazel

For full documentation on how to use bazel to generate Container images, see the [bazel-contrib/rules_oci](https://github.com/bazel-contrib/rules_oci) repository.

For documentation and example on how to create custom container images, see the [GoogleContainerTools/rules_distroless](https://github.com/GoogleContainerTools/rules_distroless) repository.

Examples can be found in the [GoogleContainerTools/rules_distroless](https://github.com/GoogleContainerTools/rules_distroless/tree/main/examples) repository.

#### Examples with Bazel

We have some examples on how to run some common application stacks in the /examples directory.
See here for:

- [Java](examples/java/BUILD)
- [Python 3](examples/python3/BUILD)
- [Go](examples/go/BUILD)
- [Node.js](examples/nodejs/BUILD)

See here for examples on how to complete some common tasks in your image:

- [Adding and running as a non-root user](examples/nonroot)
- [Including Debian Packages](https://registry.bazel.build/docs/rules_distroless#module_extension-apt)
- [Including CA certificates](https://registry.bazel.build/docs/rules_distroless#rule-cacerts)

See here for more information on how these images are [built and released](RELEASES.md).

### Base Operating System

Distroless images are based on Debian 12 (bookworm). Images are explicitly tagged with Debian version suffixes (e.g. `-debian12`). Specifying an image without the distribution will currently select `-debian12` images, but that will change in the future to a newer version of Debian. It can be useful to reference the distribution explicitly, to prevent breaking builds when the next Debian version is released.

### Operating System Updates for Security Fixes and CVEs

Distroless tracks the upstream Debian releases, using [Github actions to automatically generate a pull request when there are updates](https://github.com/GoogleContainerTools/distroless/blob/main/.github/workflows/update-deb-package-snapshots.yml).

### Debug Images

Distroless images are minimal and lack shell access. The `:debug` image set for each language provides a busybox shell to enter.

For example:

```sh
cd examples/python3/
```

edit the `Dockerfile` to change the final image to `:debug`:

```dockerfile
FROM gcr.io/distroless/python3-debian12:debug
COPY . /app
WORKDIR /app
CMD ["hello.py", "/etc"]
```

then build and launch with a shell entrypoint:

```sh
docker build -t my_debug_image .
```

```sh
$ docker run --entrypoint=sh -ti my_debug_image

/app # ls
BUILD       Dockerfile  hello.py
```

> Note: If the image you are using already has a tag, for example `gcr.io/distroless/java17-debian13:nonroot`, use the tag `debug-<existing tag>` instead, for example `gcr.io/distroless/java17-debian13:debug-nonroot`.

> Note: [ldd](http://man7.org/linux/man-pages/man1/ldd.1.html) is not installed in the base image as it's a shell script, you can copy it in or download it.

### Who uses Distroless?

- [Kubernetes](https://github.com/kubernetes/enhancements/blob/master/keps/sig-release/1729-rebase-images-to-distroless/README.md), since v1.15
- [Knative](https://knative.dev)
- [Tekton](https://tekton.dev)
- [Teleport](https://goteleport.com)
- [BloodHound](https://github.com/SpecterOps/BloodHound) by [SpecterOps](https://specterops.io/)

If your project uses Distroless, send a PR to add your project here!

## Community Discussion

- [distroless-users Google Group](https://groups.google.com/forum/#!forum/distroless-users)
- [Kubernetes slack #distroless channel](https://slack.k8s.io/)


================================================
FILE: RELEASES.md
================================================
# Release Process

Images in this repository are built and released in the `gcr.io/distroless` repository on the [Google Container Registry](https://cloud.google.com/container-registry/).

Images are automatically built and pushed every commit, according to the policy defined in [cloudbuild.yaml](.cloudbuild/cloudbuild.yaml).


================================================
FILE: SECURITY.md
================================================
# Security Policy

## Supported Versions

Distroless currently tracks debian 12 ([bookworm](https://packages.debian.org/bookworm/allpackages)) and debian 13 ([trixie](https://packages.debian.org/trixie/allpackages)) packages.

Debian package versions used for the current build are found in https://github.com/GoogleContainerTools/distroless/blob/main/private/repos/deb. It can be parsed and printed into simple json data by invoking `./knife deb-versions` at the root of this project.

## Reporting a Vulnerability

If a distroless image you are using contains a CVE or other vulnerability:
1. ensure you are using a [currently supported image](https://github.com/GoogleContainerTools/distroless#what-images-are-available)
1. find the appropriate debian security-tracker notice: `https://security-tracker.debian.org/tracker/CVE-XXXX-YYYYY`, for [example](https://security-tracker.debian.org/tracker/CVE-2022-21476).
1. check if a fix is available for the appropriate debian version in the main/security channels (ex `trixie`, `trixie (security)`).
    1. if a fix is not yet available, do not file a bug, track it in your internal tracker until one becomes available.
    1. if a fix is available *and* it has been more than 48 hours, please let the team know by creating an issue and pointing to the CVE or vulnerability disclosure.


================================================
FILE: SUPPORT_POLICY.md
================================================
Distroless currently tracks Debian on their [standard support timeline](https://wiki.debian.org/DebianReleases#Production_Releases).

The current estimation of end of life for images with the pattern:

`gcr.io/distroless/<image>-debian<version>:(latest|nonroot|debug|debug-nonroot)`

| Image       | Debian 12 EOL  | Debian 13 EOL               |
| ----------- | -------------- | --------------------------- |
| static      | Sept 2026      | debian 14 release day + 1yr |
| base        | Sept 2026      | debian 14 release day + 1yr |
| base-nossl  | Sept 2026      | debian 14 release day + 1yr |
| cc          | Sept 2026      | debian 14 release day + 1yr |
| java*       | Jan 2026       | debian 14 release day + 3mo |
| node*       | Jan 2026       | debian 14 release day + 3mo |
| python*     | April 2026     | debian 14 release day + 3mo |

\* see below for language specific runtime notes


### Java
Java will only support current LTS version distributed by debian [see here](https://wiki.debian.org/Java).

### Node
Node version support is for even numbered releases (20, 22, 24, etc) that are current, active or in LTS maintenance. For more information, [see here](https://nodejs.org/en/about/previous-releases#release-schedule).

### Python (TBD)

### Images no longer supported
A list of supported image tags is available here: https://github.com/GoogleContainerTools/distroless#what-images-are-available


================================================
FILE: WORKSPACE
================================================
load("@rules_oci//cosign:repositories.bzl", "cosign_register_toolchains")

cosign_register_toolchains(name = "oci_cosign")


================================================
FILE: base/BUILD
================================================
load(":base.bzl", "base_image", "base_image_index", "base_nossl_image", "base_nossl_image_index")
load(":config.bzl", "BASE_ARCHITECTURES", "BASE_DISTROS", "BASE_NOSSL_PACKAGES", "BASE_PACKAGES")

package(default_visibility = ["//visibility:public"])

# base nossl
[
    base_nossl_image(
        arch = arch,
        distro = distro,
        packages = BASE_NOSSL_PACKAGES[distro],
    )
    for distro in BASE_DISTROS
    for arch in BASE_ARCHITECTURES[distro]
]

[
    base_nossl_image_index(
        architectures = BASE_ARCHITECTURES[distro],
        distro = distro,
    )
    for distro in BASE_DISTROS
]

# base (with libssl)

[
    base_image(
        arch = arch,
        distro = distro,
        packages = BASE_PACKAGES[distro],
    )
    for distro in BASE_DISTROS
    for arch in BASE_ARCHITECTURES[distro]
]

[
    base_image_index(
        architectures = BASE_ARCHITECTURES[distro],
        distro = distro,
    )
    for distro in BASE_DISTROS
]


================================================
FILE: base/README.md
================================================
# Documentation for `gcr.io/distroless/base`, `gcr.io/distroless/base-nossl` and `gcr.io/distroless/static`

## Image Contents

This image contains a minimal Linux, glibc-based system. It is intended for use directly by "mostly-statically compiled" languages like Go, Rust or D.

Statically compiled applications (Go) that do not require libc can use the `gcr.io/distroless/static` image, which contains:

* ca-certificates
* A /etc/passwd entry for a root user
* A /tmp directory
* tzdata

Applications that require libc but do not need libssl can use the `gcr.io/distroless/base-nossl`, which contains all
of the packages in `gcr.io/distroless/static`, and

* glibc

Most other applications (and Go apps that require libc/cgo) should start with `gcr.io/distroless/base`, which contains all
of the packages in `gcr.io/distroless/static`, and 

* glibc
* libssl
  * for Debian 13 or later, libssl dependencies (zlib, libzstd) are also included.

Note: Debian 13 base image does not include [OpenSSL legacy algorithms](https://docs.openssl.org/3.5/man7/OSSL_PROVIDER-legacy/#operations-and-algorithms).

If you want to use them, you will need to add `openssl-legacy-provider` yourself.

## Usage

Users are expected to include their compiled application and set the correct cmd in their image.


================================================
FILE: base/base.bzl
================================================
"defines a function to replicate the container images for different distributions"

load("@container_structure_test//:defs.bzl", "container_structure_test")
load("@rules_oci//oci:defs.bzl", "oci_image", "oci_image_index")
load("//common:variables.bzl", "DEBUG_MODE", "USERS")
load("//private/util:deb.bzl", "deb")

def base_nossl_image_index(distro, architectures):
    """base nossl image index for a distro

    Args:
        distro: name of distribution
        architectures: all architectures included in index
    """
    [
        oci_image_index(
            name = "base_nossl" + mode + "_" + user + "_" + distro,
            images = [
                "base_nossl" + mode + "_" + user + "_" + arch + "_" + distro
                for arch in architectures
            ],
        )
        for user in USERS
        for mode in DEBUG_MODE
    ]

def base_image_index(distro, architectures):
    """base image index for a distro

    Args:
        distro: name of distribution
        architectures: all architectures included in index
    """
    [
        oci_image_index(
            name = "base" + mode + "_" + user + "_" + distro,
            images = [
                "base" + mode + "_" + user + "_" + arch + "_" + distro
                for arch in architectures
            ],
        )
        for user in USERS
        for mode in DEBUG_MODE
    ]

def base_nossl_image(distro, arch, packages):
    """base nossl and debug images and tests for a distro/arch

    Args:
        distro: name of the distribution
        arch: the target architecture
    """

    [
        oci_image(
            name = "base_nossl" + mode + "_" + user + "_" + arch + "_" + distro,
            base = "//static:static" + mode + "_" + user + "_" + arch + "_" + distro,
            tars = [
                deb.package(arch, distro, pkg)
                for pkg in packages
            ],
        )
        for user in USERS
        for mode in DEBUG_MODE
    ]

    # Check for common base files.
    container_structure_test(
        name = "base_nossl_" + arch + "_" + distro + "_test",
        configs = ["testdata/base.yaml"],
        image = ":base_nossl_root_" + arch + "_" + distro,
        tags = ["manual", arch],
    )

    # Check for busybox
    container_structure_test(
        name = "base_nossl_debug_" + arch + "_" + distro + "_test",
        configs = ["testdata/debug.yaml"],
        image = ":base_nossl_debug_root_" + arch + "_" + distro,
        tags = ["manual", arch],
    )

def base_image(distro, arch, packages):
    """base and debug images and tests for a distro/arch

    Args:
        distro: name of the distribution
        arch: the target architecture
    """
    LIBSSL = {
        "debian12": "libssl3",
        "debian13": "libssl3t64",
    }

    [
        oci_image(
            name = "base" + mode + "_" + user + "_" + arch + "_" + distro,
            base = "//static:static" + mode + "_" + user + "_" + arch + "_" + distro,
            tars = [
                deb.package(arch, distro, pkg)
                for pkg in packages
            ],
        )
        for user in USERS
        for mode in DEBUG_MODE
    ]

    # Check for common base files.
    container_structure_test(
        name = "base_" + arch + "_" + distro + "_test",
        configs = ["testdata/base.yaml"],
        image = ":base_root_" + arch + "_" + distro,
        tags = ["manual", arch],
    )

    # Check for busybox
    container_structure_test(
        name = "base_debug_" + arch + "_" + distro + "_test",
        configs = ["testdata/debug.yaml"],
        image = ":base_debug_root_" + arch + "_" + distro,
        tags = ["manual", arch],
    )


================================================
FILE: base/config.bzl
================================================
BASE_DISTROS = ["debian12", "debian13"]
BASE_ARCHITECTURES = {
    "debian12": ["amd64", "arm64", "arm", "s390x", "ppc64le"],
    "debian13": ["amd64", "arm64", "arm", "s390x", "ppc64le", "riscv64"],
}

BASE_PACKAGES = {
    "debian12": [
        "libc6",
        "libssl3",
    ],
    "debian13": [
        "libc6",
        "libssl3t64",
        "libzstd1",
        "zlib1g",
    ],
}

BASE_NOSSL_PACKAGES = {
    "debian12": [
        "libc6",
    ],
    "debian13": [
        "libc6",
    ],
}


================================================
FILE: base/test.sh
================================================
$RUNFILES_DIR/runtimes_common/structure_tests/ext_run.sh \
  -i bazel/base:cc \
  -t $RUNFILES_DIR/distroless/base/base.tar \
  -c $RUNFILES_DIR/distroless/base/testdata/base.yaml


================================================
FILE: base/testdata/base.yaml
================================================
schemaVersion: "1.0.0"
fileExistenceTests:
# Basic FS sanity checks.
- name: root
  path: '/'
  shouldExist: true
- name: tmp
  path: '/tmp'
  shouldExist: true
- name: passwd
  path: '/etc/passwd'
  shouldExist: true
- name: group
  path: '/etc/group'
  shouldExist: true
- name: etc-os-release
  path: '/etc/os-release'
  shouldExist: true
- name: certs
  path: '/etc/ssl/certs/ca-certificates.crt'
  shouldExist: true
- name: certs_copyright
  path: '/usr/share/doc/ca-certificates/copyright'
  shouldExist: true
- name: services
  path: '/etc/services'
  shouldExist: true
- name: tzdata_copyright
  path: '/usr/share/doc/tzdata/copyright'
  shouldExist: true
- name: tzdata_zoneinfo
  path: '/usr/share/zoneinfo'
  shouldExist: true
- name: homedir
  path: '/root'
  shouldExist: true
- name: nonroot-homedir
  path: '/home/nonroot'
  shouldExist: true
- name: dpkg-status.d
  path: '/var/lib/dpkg/status.d/libc6'
  shouldExist: true
fileContentTests:
- name: 'known users'
  path: '/etc/passwd'
  expectedContents: ['^root:x:0:0:root:/root:/sbin/nologin\nnobody:x:65534:65534:nobody:/nonexistent:/sbin/nologin\nnonroot:x:65532:65532:nonroot:/home/nonroot:/sbin/nologin\n$']
- name: 'known groups'
  path: '/etc/group'
  expectedContents: ['^root:x:0:\nnobody:x:65534:\ntty:x:5:\nstaff:x:50:\nnonroot:x:65532:\n$']
- name: '/usr/lib/os-release pretty name'
  path: '/usr/lib/os-release'
  expectedContents: ['PRETTY_NAME="Distroless"']
# /etc/os-release is a symlink to /usr/lib/os-release, make sure they match.
- name: '/etc/os-release pretty name'
  path: '/etc/os-release'
  expectedContents: ['PRETTY_NAME="Distroless"']


================================================
FILE: base/testdata/debug.yaml
================================================
schemaVersion: "1.0.0"
fileExistenceTests:
# Basic FS sanity checks.
- name: busybox
  path: '/busybox'
  shouldExist: true
commandTests:
  - name: busybox
    command: ["/busybox/busybox"]
    expectedOutput: ['BusyBox v1\.37\.0']


================================================
FILE: cc/BUILD
================================================
load(":cc.bzl", "cc_image", "cc_image_index")
load(":config.bzl", "CC_ARCHITECTURES", "CC_DISTROS", "CC_PACKAGES")

package(default_visibility = ["//visibility:public"])

[
    cc_image(
        arch = arch,
        distro = distro,
        packages = CC_PACKAGES[distro],
    )
    for distro in CC_DISTROS
    for arch in CC_ARCHITECTURES[distro]
]

[
    cc_image_index(
        architectures = CC_ARCHITECTURES[distro],
        distro = distro,
    )
    for distro in CC_DISTROS
]


================================================
FILE: cc/README.md
================================================
# Documentation for `gcr.io/distroless/cc`

## Image Contents

This image contains a minimal Linux, glibc runtime for "mostly-statically compiled" languages like Rust and D.

Specifically, the image contains everything in the [base image](../base/README.md), plus:

* libgcc1 and its dependencies.

## Usage

Users are expected to include their compiled application and set the correct CMD in their image.


================================================
FILE: cc/cc.bzl
================================================
load("@rules_oci//oci:defs.bzl", "oci_image", "oci_image_index")
load("//common:variables.bzl", "DEBUG_MODE", "USERS")
load("//private/util:deb.bzl", "deb")

def cc_image_index(distro, architectures):
    """cc image index for a distro

    Args:
        distro: name of distribution
        architectures: all architectures included in index
    """
    [
        oci_image_index(
            name = "cc" + mode + "_" + user + "_" + distro,
            images = [
                "cc" + mode + "_" + user + "_" + arch + "_" + distro
                for arch in architectures
            ],
        )
        for mode in DEBUG_MODE
        for user in USERS
    ]

def cc_image(distro, arch, packages):
    """base nossl and debug images and tests for a distro/arch

    Args:
        distro: name of the distribution
        arch: the target architecture
    """
    [
        oci_image(
            name = "cc" + mode + "_" + user + "_" + arch + "_" + distro,
            base = "//base:base" + mode + "_" + user + "_" + arch + "_" + distro,
            tars = [
                deb.package(arch, distro, pkg)
                for pkg in packages
            ],
        )
        for mode in DEBUG_MODE
        for user in USERS
    ]


================================================
FILE: cc/config.bzl
================================================
CC_DISTROS = ["debian12", "debian13"]
CC_ARCHITECTURES = {
    "debian12": ["amd64", "arm64", "arm", "s390x", "ppc64le"],
    "debian13": ["amd64", "arm64", "arm", "s390x", "ppc64le", "riscv64"],
}

CC_PACKAGES = {
    "debian12": [
        "libgomp1",
        "libstdc++6",
        "libgcc-s1",
        "gcc-12-base",
    ],
    "debian13": [
        "libgomp1",
        "libstdc++6",
        "libgcc-s1",
        "gcc-14-base",
    ],
}


================================================
FILE: common/BUILD.bazel
================================================
load("@aspect_bazel_lib//lib:tar.bzl", "tar")
load("@rules_distroless//distroless:defs.bzl", "cacerts", "group", "home", "locale", "os_release", "passwd")
load("//:distro.bzl", "ALL_DISTROS", "VERSIONS")
load("//private/util:deb.bzl", "deb")
load(":variables.bzl", "NOBODY", "NONROOT", "OS_RELEASE", "ROOT", "quote")

package(default_visibility = ["//visibility:public"])

COMMON_DISTROS = ALL_DISTROS

COMMON_ARCHITECTURES = {
    "debian12": [
        "amd64",
        "arm64",
        "arm",
        "s390x",
        "ppc64le",
    ],
    "debian13": [
        "amd64",
        "arm64",
        "arm",
        "s390x",
        "ppc64le",
        "riscv64",
    ],
}

tar(
    name = "rootfs",
    srcs = [],
    args = [
        "--format",
        "gnutar",
    ],
    compress = "gzip",
    mtree = ["./ type=dir uid=0 gid=0 mode=0755 time=0.0"],
)

tar(
    name = "tmp",
    srcs = [],
    # original tmp.tar was created on a gnutar, mimic that.
    args = [
        "--format",
        "gnutar",
    ],
    compress = "gzip",
    mtree = ["./tmp gname=root uname=root time=0.0 mode=1777 gid=0 uid=0 type=dir"],
)

[
    os_release(
        name = "os_release_%s" % dist,
        content = {
            key: quote(value.format(
                CODENAME = codename,
                VERSION = version,
            ))
            for (key, value) in OS_RELEASE.items()
        },
    )
    for (dist, codename, version) in VERSIONS
]

[
    locale(
        name = "locale_%s_%s" % (distro, arch),
        charset = "C.utf8",
        package = deb.data(arch, distro, "libc-bin"),
    )
    for distro in COMMON_DISTROS
    for arch in COMMON_ARCHITECTURES[distro]
]

[
    cacerts(
        name = "cacerts_%s_%s" % (distro, arch),
        package = deb.data(arch, distro, "ca-certificates"),
    )
    for distro in COMMON_DISTROS
    for arch in COMMON_ARCHITECTURES[distro]
]

# create /etc/group with the root, tty, and staff groups
group(
    name = "group",
    entries = [
        {
            "name": "root",  # root_group
            "gid": ROOT,
            "password": "x",
        },
        {
            "name": "nobody",  # nobody_group
            "gid": NOBODY,
            "password": "x",
        },
        {
            "name": "tty",  # tty_group
            "gid": 5,
            "password": "x",
        },
        {
            "name": "staff",  # staff_group
            "gid": 50,
            "password": "x",
        },
        {
            "name": "nonroot",  # nonroot_group
            "gid": NONROOT,
            "password": "x",
        },
    ],
)

passwd(
    name = "passwd",
    entries = [
        {
            "gecos": ["root"],
            "gid": ROOT,
            "shell": "/sbin/nologin",
            "home": "/root",
            "uid": ROOT,
            "password": "x",
            "username": "root",
        },
        {
            "gecos": ["nobody"],
            "gid": NOBODY,
            "home": "/nonexistent",
            "shell": "/sbin/nologin",
            "uid": NOBODY,
            "password": "x",
            "username": "nobody",
        },
        {
            "gecos": ["nonroot"],
            "gid": NONROOT,
            "home": "/home/nonroot",
            "shell": "/sbin/nologin",
            "uid": NONROOT,
            "password": "x",
            "username": "nonroot",
        },
    ],
)

home(
    name = "home",
    dirs = [
        {
            "home": "/root",
            "uid": ROOT,
            "gid": ROOT,
            "mode": 700,
        },
        {
            "home": "/home",
            "uid": ROOT,
            "gid": ROOT,
            "mode": 755,
        },
        {
            "home": "/home/nonroot",
            "uid": NONROOT,
            "gid": NONROOT,
            "mode": 700,
        },
    ],
)


================================================
FILE: common/variables.bzl
================================================
"common variables"

def quote(str):
    return '''"{}"'''.format(str)

OS_RELEASE = dict(
    PRETTY_NAME = "Distroless",
    NAME = "Debian GNU/Linux",
    ID = "debian",
    VERSION_ID = "{VERSION}",
    VERSION = "Debian GNU/Linux {VERSION} ({CODENAME})",
    HOME_URL = "https://github.com/GoogleContainerTools/distroless",
    SUPPORT_URL = "https://github.com/GoogleContainerTools/distroless/blob/master/README.md",
    BUG_REPORT_URL = "https://github.com/GoogleContainerTools/distroless/issues/new",
)

NOBODY = 65534
NONROOT = 65532
ROOT = 0

DEBUG_MODE = ["", "_debug"]
USERS = ["root", "nonroot"]


================================================
FILE: cosign.pub
================================================
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWZzVzkb8A+DbgDpaJId/bOmV8n7Q
OqxYbK0Iro6GzSmOzxkn+N2AKawLyXi84WSwJQBK//psATakCgAQKkNTAA==
-----END PUBLIC KEY-----


================================================
FILE: distro.bzl
================================================
VERSIONS = [
    ("debian12", "bookworm", "12"),
    ("debian13", "trixie", "13"),
]

VARIANTS = {
    "arm": "v7",
    "arm64": "v8",
}

ALL_ARCHITECTURES = ["amd64", "arm64", "arm", "s390x", "ppc64le", "riscv64"]
ALL_DISTROS = ["debian12", "debian13"]


================================================
FILE: downloader.cfg
================================================
rewrite (mirrors\.kernel\.org)/gnu/(.*) https://ftp.gnu.org/gnu/$2
rewrite (mirrors\.kernel\.org)/gnu/(.*) https://ftpmirror.gnu.org/gnu/$2


================================================
FILE: examples/BUILD
================================================
package(default_visibility = ["//visibility:public"])


================================================
FILE: examples/cc/BUILD
================================================
# Public notice: this file is for internal documentation, testing, and
# reference only. Note that repo maintainers can freely change any part of the
# repository code at any time.
load("@container_structure_test//:defs.bzl", "container_structure_test")
load("//cc:config.bzl", DISTROS = "CC_DISTROS")
load("//private/oci:defs.bzl", "cc_image")

package(default_visibility = ["//visibility:public"])

[cc_image(
    name = "hello_" + distro,
    srcs = ["hello.c"],
    base = "//cc:cc_root_amd64_" + distro,
) for distro in DISTROS]

[cc_image(
    name = "hello_cc_" + distro,
    srcs = ["hello_cc.cc"],
    base = "//cc:cc_root_amd64_" + distro,
) for distro in DISTROS]

[container_structure_test(
    name = "hello_" + distro + "_test",
    size = "small",
    configs = ["testdata/hello_" + distro + ".yaml"],
    image = ":hello_" + distro,
    tags = [
        "amd64",
        "manual",
    ],
) for distro in DISTROS]

[container_structure_test(
    name = "hello_cc_" + distro + "_test",
    size = "small",
    configs = ["testdata/hello_cc_" + distro + ".yaml"],
    image = ":hello_cc_" + distro,
    tags = [
        "amd64",
        "manual",
    ],
) for distro in DISTROS]


================================================
FILE: examples/cc/Dockerfile
================================================
FROM gcc:6 AS build-env
COPY . /app
WORKDIR /app
RUN cc hello.c -o hello

FROM gcr.io/distroless/cc
COPY --from=build-env /app /app
WORKDIR /app
CMD ["./hello"]


================================================
FILE: examples/cc/hello.c
================================================
#include <stdio.h>

int main() {
   printf("Hello from distroless C!\n");
   return 0;
}


================================================
FILE: examples/cc/hello_cc.cc
================================================
#include <iostream>

int main() {
   std::cout << "Hello from distroless C++!" << std::endl;
   return 0;
}


================================================
FILE: examples/cc/testdata/hello_cc_debian12.yaml
================================================
schemaVersion: "1.0.0"
commandTests:
  - name: hello_cc
    command: ['/hello_cc_debian12_binary']
    expectedOutput: ['Hello from distroless C\+\+!']


================================================
FILE: examples/cc/testdata/hello_cc_debian13.yaml
================================================
schemaVersion: "1.0.0"
commandTests:
  - name: hello_cc
    command: ['/hello_cc_debian13_binary']
    expectedOutput: ['Hello from distroless C\+\+!']


================================================
FILE: examples/cc/testdata/hello_debian12.yaml
================================================
schemaVersion: "1.0.0"
commandTests:
  - name: hello
    command: ['/hello_debian12_binary']
    expectedOutput: ['Hello from distroless C!']


================================================
FILE: examples/cc/testdata/hello_debian13.yaml
================================================
schemaVersion: "1.0.0"
commandTests:
  - name: hello
    command: ['/hello_debian13_binary']
    expectedOutput: ['Hello from distroless C!']


================================================
FILE: examples/go/BUILD
================================================
# Public notice: this file is for internal documentation, testing, and
# reference only. Note that repo maintainers can freely change any part of the
# repository code at any time.
load("@rules_oci//oci:defs.bzl", "oci_load")
load("//private/oci:defs.bzl", "go_image")

package(default_visibility = ["//visibility:public"])

go_image(
    name = "go_example",
    srcs = ["main.go"],
    base = "//base:base_root_amd64_debian12",
)

# Run
# bazel run //examples/go:tarball
# podman run localhost/distroless/examples/go:latest
oci_load(
    name = "tarball",
    image = ":go_example",
    repo_tags = ["distroless/examples/go:latest"],
)


================================================
FILE: examples/go/Dockerfile
================================================
FROM golang:1.22 as build

WORKDIR /go/src/app
COPY . .

RUN go mod download
RUN go vet -v
RUN go test -v

RUN CGO_ENABLED=0 go build -o /go/bin/app

FROM gcr.io/distroless/static-debian12

COPY --from=build /go/bin/app /
CMD ["/app"]


================================================
FILE: examples/go/go.mod
================================================
module github.com/GoogleContainerTools/distroless/examples/go

go 1.18


================================================
FILE: examples/go/main.go
================================================
// 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 main

import "fmt"

func main() {
	fmt.Println("Hello, world!")
}


================================================
FILE: examples/go/main_test.go
================================================
package main

import (
	"testing"
)

func Test1(t *testing.T) {
}


================================================
FILE: examples/java/BUILD
================================================
# Public notice: this file is for internal documentation, testing, and
# reference only. Note that repo maintainers can freely change any part of the
# repository code at any time.
load("@container_structure_test//:defs.bzl", "container_structure_test")
load("//private/oci:defs.bzl", "java_image")

package(default_visibility = ["//visibility:public"])

JAVA_VERSIONS_PER_DISTRO = [
    ("17", "debian13"),
]

[
    java_image(
        name = "hello_java" + java_version + "_" + user + "_" + distro,
        srcs = ["HelloJava.java"],
        base = "//java:java" + java_version + "_" + user + "_amd64" + "_" + distro,
        main_class = "examples.HelloJava",
    )
    for user in [
        "root",
        "nonroot",
    ]
    for java_version, distro in JAVA_VERSIONS_PER_DISTRO
]

[
    container_structure_test(
        name = "hello_java" + java_version + "_" + user + "_" + distro + "_test",
        size = "small",
        configs = ["testdata/hello_" + user + "_" + distro + ".yaml"],
        image = ":hello_java" + java_version + "_" + user + "_" + distro,
        tags = [
            "amd64",
            "manual",
        ],
    )
    for user in [
        "root",
        "nonroot",
    ]
    for java_version, distro in JAVA_VERSIONS_PER_DISTRO
]


================================================
FILE: examples/java/Dockerfile
================================================
FROM eclipse-temurin:17-jdk AS build-env
COPY . /app/examples
WORKDIR /app
RUN javac examples/*.java
RUN jar cfe main.jar examples.HelloJava examples/*.class 

FROM gcr.io/distroless/java17-debian13
COPY --from=build-env /app /app
WORKDIR /app
CMD ["main.jar"]


================================================
FILE: examples/java/HelloJava.java
================================================
package examples;

public class HelloJava {
    public static void main(String[] args) {
        System.out.println("Hello world");
    }
}


================================================
FILE: examples/java/testdata/hello_nonroot_debian13.yaml
================================================
schemaVersion: "1.0.0"
commandTests:
  - name: hello
    command: ['/usr/bin/java', '-cp', '/*', 'examples.HelloJava']
    expectedOutput: ['Hello world']


================================================
FILE: examples/java/testdata/hello_root_debian13.yaml
================================================
schemaVersion: "1.0.0"
commandTests:
  - name: hello
    command: ['/usr/bin/java', '-cp', '/*', 'examples.HelloJava']
    expectedOutput: ['Hello world']


================================================
FILE: examples/nodejs/BUILD
================================================
# Public notice: this file is for internal documentation, testing, and
# reference only. Note that repo maintainers can freely change any part of the
# repository code at any time.
load("@container_structure_test//:defs.bzl", "container_structure_test")
load("@rules_oci//oci:defs.bzl", "oci_image")
load("//private/util:tar.bzl", "tar")

package(default_visibility = ["//visibility:public"])

ARCHITECTURES = [
    "amd64",
    "arm64",
]

DISTROS = [
    "debian13",
]

# These examples are adapted from:
# https://howtonode.org/hello-node

tar(
    name = "hello_tar",
    srcs = [
        "hello.js",
    ],
)

tar(
    name = "hello_http_tar",
    srcs = [
        "hello_http.js",
    ],
)

[
    oci_image(
        name = "hello_" + user + "_" + arch + "_" + distro,
        base = "//nodejs:nodejs22_" + user + "_" + arch + "_" + distro,
        cmd = ["hello.js"],
        tars = [":hello_tar"],
    )
    for user in [
        "root",
        "nonroot",
    ]
    for arch in ARCHITECTURES
    for distro in DISTROS
]

[
    oci_image(
        name = "hello_http_" + user + "_" + arch + "_" + distro,
        base = "//nodejs:nodejs22_" + user + "_" + arch + "_" + distro,
        cmd = ["hello_http.js"],
        tars = [":hello_http_tar"],
    )
    for user in [
        "root",
        "nonroot",
    ]
    for arch in ARCHITECTURES
    for distro in DISTROS
]

[
    container_structure_test(
        name = "hello_" + user + "_" + arch + "_" + distro + "_test",
        configs = ["testdata/hello.yaml"],
        image = ":hello_" + user + "_" + arch + "_" + distro,
        tags = [
            arch,
            "manual",
        ],
    )
    for user in [
        "root",
        "nonroot",
    ]
    for arch in ARCHITECTURES
    for distro in DISTROS
]


================================================
FILE: examples/nodejs/Dockerfile
================================================
FROM node:22 AS build-env
COPY . /app
WORKDIR /app

RUN npm ci --omit=dev

FROM gcr.io/distroless/nodejs22-debian13
COPY --from=build-env /app /app
WORKDIR /app
CMD ["hello.js"]


================================================
FILE: examples/nodejs/hello.js
================================================

console.log("Hello World");


================================================
FILE: examples/nodejs/hello_http.js
================================================
// Load the http module to create an http server.
var http = require('http');

// Configure our HTTP server to respond with Hello World to all requests.
var server = http.createServer(function (request, response) {
  response.writeHead(200, {"Content-Type": "text/plain"});
  response.end("Hello World\n");
});

// Listen on port 8000, IP defaults to 127.0.0.1
server.listen(8000);

// Put a friendly message on the terminal
console.log("Server running at http://127.0.0.1:8000/");


================================================
FILE: examples/nodejs/node-express/Dockerfile
================================================
FROM node:22 AS build-env
ADD . /app
WORKDIR /app
RUN npm install --omit=dev

FROM gcr.io/distroless/nodejs22-debian13
COPY --from=build-env /app /app
WORKDIR /app
EXPOSE 3000
CMD ["hello_express.js"]


================================================
FILE: examples/nodejs/node-express/hello_express.js
================================================
const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => res.send('Hello World!'))

app.listen(port, () => console.log(`Example app listening on port ${port}!`))


================================================
FILE: examples/nodejs/node-express/package.json
================================================
{
  "name": "distroless-express",
  "version": "1.0.0",
  "description": "Distroless express node.js",
  "repository": {
    "type": "git",
    "url": "https://github.com/GoogleContainerTools/distroless.git"
  },
  "dependencies": {
    "express": "4.20.0"
  },
  "author": "Bryant Hagadorn",
  "license": "ISC"
}

================================================
FILE: examples/nodejs/package.json
================================================
{
  "name": "nodejs",
  "version": "1.0.0",
  "description": "Example",
  "main": "hello.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


================================================
FILE: examples/nodejs/testdata/hello.yaml
================================================
schemaVersion: "1.0.0"
commandTests:
  - name: hello
    command: ["/nodejs/bin/node", "/hello.js"]
    expectedOutput: ['Hello World']


================================================
FILE: examples/nonroot/BUILD
================================================
# Public notice: this file is for internal documentation, testing, and
# reference only. Note that repo maintainers can freely change any part of the
# repository code at any time.
load("@container_structure_test//:defs.bzl", "container_structure_test")
load("@rules_distroless//distroless:defs.bzl", "home", "passwd")
load("@rules_go//go:def.bzl", "go_binary")
load("@rules_oci//oci:defs.bzl", "oci_image")
load("//:distro.bzl", DISTROS = "ALL_DISTROS")
load("//private/util:tar.bzl", "tar")

# Create a passwd file and home directory with a nonroot user and uid.
passwd(
    name = "passwd",
    entries = [
        {
            "gecos": ["nonroot"],
            "gid": 1000,
            "home": "/home",
            "shell": "/bin/bash",
            "uid": 1002,
            "username": "nonroot",
        },
    ],
)

home(
    name = "home",
    dirs = [
        {
            "home": "/home",
            "uid": 1002,
            "gid": 1000,
        },
    ],
)

# Include it in our image as a tar.
oci_image(
    name = "passwd_image",
    base = "//base:base_root_amd64_debian12",
    tars = [
        ":passwd",
        ":home",
    ],
    user = "nonroot",
    visibility = ["//visibility:private"],
)

# Simple go program to print out the username and uid.
go_binary(
    name = "user",
    srcs = ["testdata/user.go"],
    goarch = "amd64",
    # Test image is linux based
    goos = "linux",
    pure = "on",
)

tar(
    name = "user_tar",
    srcs = [":user"],
)

[oci_image(
    name = "check_user_image_" + distro,
    base = ":passwd_image",
    tars = [":user_tar"],
    visibility = ["//visibility:private"],
) for distro in DISTROS]

# Test to verify this works :)
[container_structure_test(
    name = "check_user_" + distro + "_test",
    configs = ["testdata/user.yaml"],
    image = ":check_user_image_" + distro,
    tags = [
        "amd64",
        "manual",
    ],
    visibility = ["//visibility:private"],
) for distro in DISTROS]


================================================
FILE: examples/nonroot/testdata/user.go
================================================
// 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 main

import (
	"fmt"
	"io/ioutil"
	"os"
	"strconv"
	"strings"
)

func main() {
	// We can't use user.Current here because it requires cgo.
	uid := strconv.Itoa(os.Getuid())

	passwd, err := ioutil.ReadFile("/etc/passwd")
	if err != nil {
		fmt.Printf("Error reading /etc/passwd: %v", err)
		return
	}
	for _, line := range strings.Split(string(passwd), "\n") {
		if len(line) == 0 {
			continue
		}
		entries := strings.Split(line, ":")
		name, id := entries[0], entries[2]
		if id == uid {
			fmt.Println("User:", name)
			fmt.Println("Uid:", id)
			return
		}
	}
}


================================================
FILE: examples/nonroot/testdata/user.yaml
================================================
schemaVersion: "1.0.0"
commandTests:
  - name: user
    command: ['/user']
    expectedOutput: ['User: nonroot', 'Uid: 1002']


================================================
FILE: examples/python3/BUILD
================================================
# Public notice: this file is for internal documentation, testing, and
# reference only. Note that repo maintainers can freely change any part of the
# repository code at any time.
load("@rules_oci//oci:defs.bzl", "oci_image", "oci_load")
load("//private/util:tar.bzl", "tar")
load("//python3:config.bzl", DISTROS = "PYTHON_DISTROS")

tar(
    name = "hello_py",
    srcs = ["hello.py"],
)

# This example runs a python program that walks the filesystem under "/etc" and prints every filename.
[
    oci_image(
        name = "hello_" + distro,
        base = "//python3:python3_root_amd64_" + distro,
        cmd = [
            "hello.py",
            "/etc",
        ],
        tars = [
            ":hello_py",
        ],
    )
    for distro in DISTROS
]

# Run
# bazel run //examples/python3:tarball_debian12
# podman run localhost/distroless/examples/py:latest
[
    oci_load(
        name = "tarball_" + distro,
        image = ":hello_" + distro,
        repo_tags = ["distroless/examples/py:latest"],
    )
    for distro in DISTROS
]


================================================
FILE: examples/python3/Dockerfile
================================================
FROM python:3-slim AS build-env
COPY . /app
WORKDIR /app

FROM gcr.io/distroless/python3
COPY --from=build-env /app /app
WORKDIR /app
CMD ["hello.py", "/etc"]


================================================
FILE: examples/python3/hello.py
================================================
#!/bin/python

# 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.

import argparse
import os

parser = argparse.ArgumentParser()
parser.add_argument('root', type=str,
                    help='The root directory to walk.')

def main(args):
    """Prints the files that are inside the container, rooted at the first argument."""
    for dirpath, _, files in os.walk(args.root):
        for f in files:
            print(os.path.join(dirpath, f))

if __name__ == "__main__":
    main(parser.parse_args())


================================================
FILE: examples/python3-requirements/Dockerfile
================================================
# Build a virtualenv using the appropriate Debian release
# * Install python3-venv for the built-in Python3 venv module (not installed by default)
# * Install gcc libpython3-dev to compile C Python modules
# * In the virtualenv: Update pip setuputils and wheel to support building new packages
FROM debian:12-slim AS build
RUN apt-get update && \
    apt-get install --no-install-suggests --no-install-recommends --yes python3-venv gcc libpython3-dev && \
    python3 -m venv /venv && \
    /venv/bin/pip install --upgrade pip setuptools wheel

# Build the virtualenv as a separate step: Only re-execute this step when requirements.txt changes
FROM build AS build-venv
COPY requirements.txt /requirements.txt
RUN /venv/bin/pip install --disable-pip-version-check -r /requirements.txt

# Copy the virtualenv into a distroless image
FROM gcr.io/distroless/python3-debian12
COPY --from=build-venv /venv /venv
COPY . /app
WORKDIR /app
ENTRYPOINT ["/venv/bin/python3", "psutil_example.py"]


================================================
FILE: examples/python3-requirements/README.md
================================================
# Python 3 with requirements.txt

This is a Python 3 application that specifies third-party dependencies using requirements.txt. The
psutil module it uses is a C module that must be compiled. This is the most annoying kind of
dependency, since you need to build it in an environment where the C library and Python version
match where it will run.

It builds the final container in three stages:

1. `build`: Set up a Debian build environment that can compile Python C modules.
2. `build-venv`: Create a virtualenv using `requirements.txt`.
3. Output: Copy the venv and the code and build the final image.

The first step is only re-executed if you edit `Dockerfile`. The second step is only re-executed
if you change requirements.txt. The final step is very fast and will change on every code edit.


## Build and run

1. Build the image: `docker build . --tag=psutil-example`
2. Run it! `docker run --rm psutil-example`


## Example output

```
RSS: 13.0 MiB;  SHARED: 5.7 MiB; VIRTUAL: 19.9 MiB
```


## TODO: Bazel

It would be nice if we could show how to build this container with Bazel, but I don't actually know
how to do that.


================================================
FILE: examples/python3-requirements/psutil_example.py
================================================
#!/usr/bin/env python3

# Distroless's test.sh runs pylint on all python files, but this module will not exist
# pylint: disable=import-error
import psutil


def mib(total_bytes):
    '''Converts bytes to MiB (float).'''
    return total_bytes / 1024 / 1024


def main():
    current_process = psutil.Process()
    memory = current_process.memory_info()
    print(f'RSS: {mib(memory.rss):.1f} MiB;  SHARED: {mib(memory.shared):.1f} MiB; VIRTUAL: {mib(memory.vms):.1f} MiB')


if __name__ == '__main__':
    main()


================================================
FILE: examples/python3-requirements/requirements.txt
================================================
# this version of psutil does not ship binary wheels: must be compiled
psutil==5.6.6


================================================
FILE: examples/rust/.dockerignore
================================================
.git/
target/


================================================
FILE: examples/rust/BUILD
================================================
# Public notice: this file is for internal documentation, testing, and
# reference only. Note that repo maintainers can freely change any part of the
# repository code at any time.

# Rust
load("//private/oci:defs.bzl", "rust_image")

package(default_visibility = ["//visibility:public"])

# NOTE: Bazel Rust rules don't support cross-compilation yet,
# so at this time it's required that you build on the same platform as the image
rust_image(
    name = "rust_example",
    srcs = ["src/main.rs"],
    base = "//cc:cc_root_amd64_debian12",
    tags = [
        "amd64",
        "manual",
    ],
)


================================================
FILE: examples/rust/Cargo.toml
================================================
[package]
name = "hello-world-distroless"
version = "0.1.0"
authors = ["Bryant Hagadorn <blhagadorn@gmail.com>"]

[dependencies]


================================================
FILE: examples/rust/Dockerfile
================================================
FROM rust:1 as build-env
WORKDIR /app
COPY . /app
RUN cargo build --release

FROM gcr.io/distroless/cc-debian12
COPY --from=build-env /app/target/release/hello-world-distroless /
CMD ["./hello-world-distroless"]


================================================
FILE: examples/rust/src/main.rs
================================================
fn main() {
    println!("Hello World!");
}


================================================
FILE: examples/test.sh
================================================
#!/usr/bin/env bash

set -euo pipefail

for d in examples/*; do
	# Skip non-directories.
	if [[ ! -d "$d" ]]; then continue; fi

	# Skip if the directory doesn't have a Dockerfile.
	if [[ ! -f $d/Dockerfile ]]; then continue; fi

	# Skip these non-working examples.
	if [[ $d == "examples/cc" ]]; then continue; fi

	# Build the Dockerfile and run the image, or fail.
	echo ====================================
	echo = building $d
	echo ====================================
	docker build -t $d ./$d/
	docker run $d
done


================================================
FILE: experimental/BUILD
================================================


================================================
FILE: experimental/busybox/BUILD
================================================
load("//:distro.bzl", BUSYBOX_ARCHITECTURES = "ALL_ARCHITECTURES")
load(":commands.bzl", "busybox_layer")

package(default_visibility = ["//static:__subpackages__"])

[
    busybox_layer(
        name = "busybox_" + arch,
        busybox = "@busybox_" + arch + "//:file",
        compress = "gzip",
    )
    for arch in BUSYBOX_ARCHITECTURES
]


================================================
FILE: experimental/busybox/commands.bzl
================================================
load("@aspect_bazel_lib//lib:tar.bzl", "tar")

BUSYBOX_COMMANDS = [
    "[",
    "[[",
    "acpid",
    "add-shell",
    "addgroup",
    "adduser",
    "adjtimex",
    "ar",
    "arp",
    "arping",
    "ash",
    "awk",
    "base64",
    "basename",
    "blkdiscard",
    "blkid",
    "blockdev",
    "bootchartd",
    "brctl",
    "bunzip2",
    "bzcat",
    "bzip2",
    "cal",
    "cat",
    "chat",
    "chattr",
    "chgrp",
    "chmod",
    "chown",
    "chpasswd",
    "chpst",
    "chroot",
    "chrt",
    "chvt",
    "cksum",
    "clear",
    "cmp",
    "comm",
    "conspy",
    "cp",
    "cpio",
    "crond",
    "crontab",
    "cryptpw",
    "cttyhack",
    "cut",
    "date",
    "dc",
    "dd",
    "deallocvt",
    "delgroup",
    "deluser",
    "depmod",
    "devmem",
    "df",
    "dhcprelay",
    "diff",
    "dirname",
    "dmesg",
    "dnsd",
    "dnsdomainname",
    "dos2unix",
    "dpkg",
    "dpkg-deb",
    "du",
    "dumpkmap",
    "dumpleases",
    "echo",
    "ed",
    "egrep",
    "eject",
    "env",
    "envdir",
    "envuidgid",
    "expand",
    "expr",
    "factor",
    "fakeidentd",
    "false",
    "fatattr",
    "fbset",
    "fbsplash",
    "fdflush",
    "fdformat",
    "fdisk",
    "fgconsole",
    "fgrep",
    "find",
    "findfs",
    "flash_eraseall",
    "flash_lock",
    "flash_unlock",
    "flashcp",
    "flock",
    "fold",
    "free",
    "freeramdisk",
    "fsck",
    "fsck.minix",
    "fsfreeze",
    "fstrim",
    "fsync",
    "ftpd",
    "ftpget",
    "ftpput",
    "fuser",
    "getopt",
    "getty",
    "grep",
    "groups",
    "gunzip",
    "gzip",
    "halt",
    "hd",
    "hdparm",
    "head",
    "hexdump",
    "hostid",
    "hostname",
    "httpd",
    "hush",
    "hwclock",
    "i2cdetect",
    "i2cdump",
    "i2cget",
    "i2cset",
    "id",
    "ifconfig",
    "ifenslave",
    "ifplugd",
    "inetd",
    "init",
    "inotifyd",
    "insmod",
    "install",
    "ionice",
    "iostat",
    "ip",
    "ipaddr",
    "ipcalc",
    "ipcrm",
    "ipcs",
    "iplink",
    "ipneigh",
    "iproute",
    "iprule",
    "iptunnel",
    "kbd_mode",
    "kill",
    "killall",
    "killall5",
    "klogd",
    "last",
    "less",
    "link",
    "linux32",
    "linux64",
    "linuxrc",
    "ln",
    "loadfont",
    "loadkmap",
    "logger",
    "login",
    "logname",
    "losetup",
    "lpd",
    "lpq",
    "lpr",
    "ls",
    "lsattr",
    "lsmod",
    "lsof",
    "lspci",
    "lsscsi",
    "lsusb",
    "lzcat",
    "lzma",
    "lzop",
    "lzopcat",
    "makedevs",
    "makemime",
    "man",
    "md5sum",
    "mdev",
    "mesg",
    "microcom",
    "mkdir",
    "mkdosfs",
    "mke2fs",
    "mkfifo",
    "mkfs.ext2",
    "mkfs.minix",
    "mkfs.vfat",
    "mknod",
    "mkpasswd",
    "mkswap",
    "mktemp",
    "modinfo",
    "modprobe",
    "more",
    "mount",
    "mountpoint",
    "mpstat",
    "mt",
    "mv",
    "nameif",
    "nbd-client",
    "nc",
    "netstat",
    "nice",
    "nl",
    "nmeter",
    "nohup",
    "nproc",
    "ntpd",
    "od",
    "openvt",
    "partprobe",
    "passwd",
    "paste",
    "patch",
    "pgrep",
    "pidof",
    "ping",
    "ping6",
    "pipe_progress",
    "pivot_root",
    "pkill",
    "pmap",
    "popmaildir",
    "poweroff",
    "powertop",
    "printenv",
    "printf",
    "ps",
    "pscan",
    "pstree",
    "pwd",
    "pwdx",
    "raidautorun",
    "rdate",
    "rdev",
    "readlink",
    "readprofile",
    "realpath",
    "reboot",
    "reformime",
    "remove-shell",
    "renice",
    "reset",
    "resize",
    "rev",
    "rm",
    "rmdir",
    "rmmod",
    "route",
    "rpm",
    "rpm2cpio",
    "rtcwake",
    "run-parts",
    "runlevel",
    "runsv",
    "runsvdir",
    "rx",
    "script",
    "scriptreplay",
    "sed",
    "sendmail",
    "seq",
    "setarch",
    "setconsole",
    "setfont",
    "setkeycodes",
    "setlogcons",
    "setpriv",
    "setserial",
    "setsid",
    "setuidgid",
    "sh",
    "sha1sum",
    "sha256sum",
    "sha3sum",
    "sha512sum",
    "showkey",
    "shred",
    "shuf",
    "slattach",
    "sleep",
    "smemcap",
    "softlimit",
    "sort",
    "split",
    "ssl_client",
    "start-stop-daemon",
    "stat",
    "strings",
    "stty",
    "su",
    "sulogin",
    "sum",
    "sv",
    "svc",
    "svlogd",
    "swapoff",
    "swapon",
    "switch_root",
    "sync",
    "sysctl",
    "syslogd",
    "tac",
    "tail",
    "tar",
    "taskset",
    "tcpsvd",
    "tee",
    "telnet",
    "telnetd",
    "test",
    "tftp",
    "tftpd",
    "time",
    "timeout",
    "top",
    "touch",
    "tr",
    "traceroute",
    "traceroute6",
    "true",
    "truncate",
    "tty",
    "ttysize",
    "tunctl",
    "tune2fs",
    "ubiattach",
    "ubidetach",
    "ubimkvol",
    "ubirename",
    "ubirmvol",
    "ubirsvol",
    "ubiupdatevol",
    "udhcpc",
    "udhcpd",
    "udpsvd",
    "uevent",
    "umount",
    "uname",
    "uncompress",
    "unexpand",
    "uniq",
    "unix2dos",
    "unlink",
    "unlzma",
    "unlzop",
    "unxz",
    "unzip",
    "uptime",
    "users",
    "usleep",
    "uudecode",
    "uuencode",
    "vconfig",
    "vi",
    "vlock",
    "volname",
    "w",
    "wall",
    "watch",
    "watchdog",
    "wc",
    "wget",
    "which",
    "who",
    "whoami",
    "whois",
    "xargs",
    "xxd",
    "xz",
    "xzcat",
    "yes",
    "zcat",
    "zcip",
]

BUSYBOX_ARCHIVE_BUILD = """\
filegroup(
    name = "file",
    srcs = ["bin/busybox"],
    visibility = ["//visibility:public"]
)
"""

def busybox_layer(busybox, **kwargs):
    tar(
        srcs = [busybox],
        mtree = [
            "./busybox/ uid=0 gid=0 mode=0755 time=0.0 type=dir",
            "./busybox/busybox uid=0 gid=0 mode=0755 time=0.0 type=file content=$(location {})".format(busybox),
        ] + [
            "./busybox/{cmd} uid=0 gid=0 mode=0755 time=0.0 type=link link=/busybox/busybox".format(cmd = cmd)
            for cmd in BUSYBOX_COMMANDS
        ],
        **kwargs
    )


================================================
FILE: java/BUILD
================================================
load(":config.bzl", "JAVA_ARCHITECTURES", "JAVA_BASE_PACKAGES", "JAVA_DISTROS")
load(":java.bzl", "java_base_image", "java_base_image_index", "java_image", "java_image_index")

package(default_visibility = ["//visibility:public"])

# publishable java base images
[
    java_base_image(
        distro,
        arch,
        JAVA_BASE_PACKAGES[distro],
    )
    for distro in JAVA_DISTROS
    for arch in JAVA_ARCHITECTURES[distro]
]

[
    java_base_image_index(
        distro,
        JAVA_ARCHITECTURES[distro],
    )
    for distro in JAVA_DISTROS
]

# publishable java temurin images from adoptium deb repository
ADOPTIUM_DEB_PER_DISTRO = [
    ("17", "debian13"),
    ("21", "debian13"),
    ("25", "debian13"),
]

[
    java_image(distro, java_version, arch)
    for java_version, distro in ADOPTIUM_DEB_PER_DISTRO
    for arch in JAVA_ARCHITECTURES[distro]
]

# all image indexes
[
    java_image_index(
        distro,
        java_version,
        JAVA_ARCHITECTURES[distro],
    )
    for java_version, distro in ADOPTIUM_DEB_PER_DISTRO
]


================================================
FILE: java/README.md
================================================
# Documentation for `gcr.io/distroless/java`

## Image Contents

This image contains a minimal Linux, OpenJDK-based runtime.

Specifically, the image contains everything in the [base image](../base/README.md), plus:

* Temurin OpenJDK 17 (`gcr.io/distroless/java17-debian13`) and its dependencies.
* Temurin OpenJDK 21 (`gcr.io/distroless/java21-debian13`) and its dependencies
* Temurin OpenJDK 25 (`gcr.io/distroless/java25-debian13`) and its dependencies

## Usage

The entrypoint of this image is set to the equivalent of "java -jar", so this image expects users to supply a path to a JAR file in the CMD.

## Add another JAVA version

To support a new JAVA version you need to do the following steps:
- Add the new version to `ADOPTIUM_DEB_PER_DISTRO` in [java/BUILD](BUILD).
- Add the new version to `JAVA_MAJOR_VERSIONS` in [java/config.bzl](config.bzl).
- Add two yaml files in the [java/testdata](testdata) folder to prepare the tests, you can take inspiration on the other files.

You can then ensure everything works on this folder with `bazel build //java:...`


================================================
FILE: java/config.bzl
================================================
JAVA_DISTROS = ["debian13"]
JAVA_ARCHITECTURES = {
    "debian13": ["amd64", "arm64", "s390x", "ppc64le", "riscv64"],
}
JAVA_MAJOR_VERSIONS = {
    "debian13": ["17", "21", "25"],
}

JAVA_BASE_PACKAGES = {
    # debian 13 - temurin ships with libharfbuzz already
    "debian13": [
        "zlib1g",
        "libjpeg62-turbo",
        "liblcms2-2",
        "libfreetype6",
        "fonts-dejavu-core",
        "fontconfig-config",
        "libexpat1",
        "libfontconfig1",
        "libuuid1",
        "libbrotli1",
        "libcrypt1",
        "libstdc++6",
        "libgcc-s1",
        "gcc-14-base",
        "libpng16-16t64",
        "libbz2-1.0",
    ],
}


================================================
FILE: java/control
================================================
Package: Eclipse Temurin
Version: {{VERSION}}
Architecture: {{ARCHITECTURE}}
Maintainer: Adoptium Working Group <https://adoptium.net/>
Homepage: https://adoptium.net
SHA256: {{SHA256}}
Description: Eclipse Temurin is the name of the OpenJDK distribution from Adoptium.


================================================
FILE: java/java.bzl
================================================
"java image definitions"

load("@container_structure_test//:defs.bzl", "container_structure_test")
load("@rules_oci//oci:defs.bzl", "oci_image", "oci_image_index")
load("//common:variables.bzl", "DEBUG_MODE", "USERS")
load("//java:jre_ver.bzl", "jre_ver")
load("//private/oci:defs.bzl", oci_java_image = "java_image")
load("//private/util:deb.bzl", "deb")
load("//private/util:java_cacerts.bzl", "java_cacerts")
load("//private/util:tar.bzl", "tar")

def ca_certs(distro, arch):
    """java ca certs for a specific arch and distro
    """

    if native.existing_rule("cacerts_java_" + arch + "_" + distro):
        return

    java_cacerts(
        name = "cacerts_java_" + arch + "_" + distro,
        archive = "//common:cacerts_" + distro + "_" + arch,
    )

def java_base_image_index(distro, architectures):
    """java base image index for a distro

    Args:
        distro: name of distribution
        architectures: all architectures included in index
    """
    for mode in DEBUG_MODE:
        for user in USERS:
            oci_image_index(
                name = "java_base" + mode + "_" + user + "_" + distro,
                images = [
                    "java_base" + mode + "_" + user + "_" + arch + "_" + distro
                    for arch in architectures
                ],
            )

def java_base_image(distro, arch, packages):
    """java base for a distro/arch

    Args:
        distro: name of distribution
        arch: the target arch
        packages: to add to the image (from a debianX_java repo)
    """

    # pre-req
    ca_certs(distro, arch)

    for mode in DEBUG_MODE:
        for user in USERS:
            oci_image(
                name = "java_base" + mode + "_" + user + "_" + arch + "_" + distro,
                base = "//base:base_nossl" + mode + "_" + user + "_" + arch + "_" + distro,
                env = {"LANG": "C.UTF-8"},
                tars = [
                    deb.package(arch, distro, pkg, "java")
                    for pkg in packages
                ] + [
                    "//common:locale_" + distro + "_" + arch,
                    ":cacerts_java_" + arch + "_" + distro,
                ],
            )

    for mode in DEBUG_MODE:
        for user in USERS:
            container_structure_test(
                name = "java_base" + mode + "_" + user + "_" + arch + "_" + distro + "_test",
                configs = ["testdata/java_base" + mode + ".yaml"],
                image = ":java_base" + mode + "_" + user + "_" + arch + "_" + distro,
                tags = [
                    arch,
                    "manual",
                ],
            )

def java_image_index(distro, java_version, architectures):
    """java and debug image indexes (applies to all java_image builds)

    Args:
        distro: name of distribution
        java_version: version of java
        architectures: all architectures included in index
    """
    for user in USERS:
        for mode in DEBUG_MODE:
            oci_image_index(
                name = "java" + java_version + mode + "_" + user + "_" + distro,
                images = [
                    "java" + java_version + mode + "_" + user + "_" + arch + "_" + distro
                    for arch in architectures
                ],
            )

def java_image(distro, java_version, arch):
    """java images from adoptium temurin deb distribution

    Args:
        distro: name of distribution
        java_version: version of java
        arch: the target arch
    """

    # intermediary rule to configure jre symlinks
    tar(
        name = "temurin_" + java_version + "_jre_" + arch + "_" + distro,
        extension = "tar.gz",
        symlinks = {
            "/usr/bin/java": "/usr/lib/jvm/temurin-" + java_version + "-jre-" + arch + "/bin/java",
            "/etc/ssl/certs/adoptium/cacerts": "/etc/ssl/certs/java/cacerts",
        },
        deps = [
            deb.package(
                arch,
                distro,
                "temurin-" + java_version + "-jre",
                "adoptium",
            ),
        ],
    )

    # intermediary rules to configure jdk symlinks
    tar(
        name = "temurin_" + java_version + "_jdk_" + arch + "_" + distro,
        extension = "tar.gz",
        symlinks = {
            "/usr/bin/java": "/usr/lib/jvm/temurin-" + java_version + "-jdk-" + arch + "/bin/java",
            "/etc/ssl/certs/adoptium/cacerts": "/etc/ssl/certs/java/cacerts",
        },
        deps = [
            deb.package(
                arch,
                distro,
                "temurin-" + java_version + "-jdk",
                "adoptium",
            ),
        ],
    )

    # standard image builds
    for user in USERS:
        oci_image(
            name = "java" + java_version + "_" + user + "_" + arch + "_" + distro,
            base = ":java_base_" + user + "_" + arch + "_" + distro,
            # We expect users to use:
            # cmd = ["/path/to/deploy.jar", "--option1", ...]
            entrypoint = [
                "/usr/bin/java",
                "-jar",
            ],
            env = {
                "JAVA_VERSION": jre_ver(deb.version(
                    arch,
                    distro,
                    "temurin-" + java_version + "-jre",
                    "adoptium",
                )),
            },
            tars = [
                # we use system certs, but we might want to pull this out of the distro
                # like we did for the github released temurin
                ":temurin_" + java_version + "_jre_" + arch + "_" + distro,
            ],
        )

    # debug image builds
    for user in USERS:
        oci_image(
            name = "java" + java_version + "_debug_" + user + "_" + arch + "_" + distro,
            base = ":java_base_debug_" + user + "_" + arch + "_" + distro,
            # We expect users to use:
            # cmd = ["/path/to/deploy.jar", "--option1", ...]
            entrypoint = [
                "/usr/bin/java",
                "-jar",
            ],
            env = {
                "JAVA_VERSION": jre_ver(deb.version(
                    arch,
                    distro,
                    "temurin-" + java_version + "-jdk",
                    "adoptium",
                )),
            },
            tars = [
                # we use system certs, but we might want to pull this out of the distro
                # like we did for the github released temurin
                ":cacerts_java_" + arch + "_" + distro,
                ":temurin_" + java_version + "_jdk_" + arch + "_" + distro,
            ],
        )

    java_tests(distro, java_version, arch)

def java_tests(distro, java_version, arch):
    """Tests for java images.

    Args:
        distro: name of distribution
        java_version: version of java
        arch: the target arch
    """
    for user in USERS:
        container_structure_test(
            name = "java" + java_version + "_" + user + "_" + arch + "_" + distro + "_test",
            configs = ["testdata/java" + java_version + "_" + distro + ".yaml"],
            image = ":java" + java_version + "_" + user + "_" + arch + "_" + distro,
            tags = [
                arch,
                "manual",
            ],
        )

    for user in USERS:
        container_structure_test(
            name = "java" + java_version + "_debug_" + user + "_" + arch + "_" + distro + "_test",
            configs = ["testdata/java" + java_version + "_debug" + "_" + distro + ".yaml"],
            image = ":java" + java_version + "_debug_" + user + "_" + arch + "_" + distro,
            tags = [
                arch,
                "manual",
            ],
        )

    if arch == "amd64":
        for user in USERS:
            oci_java_image(
                name = "check_certs_java" + java_version + "_" + user + "_" + distro,
                srcs = ["testdata/CheckCerts.java"],
                base = "//java:java" + java_version + "_" + user + "_amd64_" + distro,
                main_class = "testdata.CheckCerts",
            )

        for user in USERS:
            container_structure_test(
                name = "check_certs_java" + java_version + "_" + user + "_" + distro + "_test",
                configs = ["testdata/java_certs.yaml"],
                image = ":check_certs_java" + java_version + "_" + user + "_" + distro,
                tags = [
                    "amd64",
                    "manual",
                ],
            )

        for user in USERS:
            oci_java_image(
                name = "check_encoding_java" + java_version + "_" + user + "_" + distro,
                srcs = ["testdata/CheckEncoding.java"],
                base = "//java:java" + java_version + "_" + user + "_amd64_" + distro,
                main_class = "testdata.CheckEncoding",
            )

        for user in USERS:
            container_structure_test(
                name = "check_encoding_java" + java_version + "_" + user + "_" + distro + "_test",
                configs = ["testdata/java_encoding.yaml"],
                image = ":check_encoding_java" + java_version + "_" + user + "_" + distro,
                tags = [
                    "amd64",
                    "manual",
                ],
            )

        for user in USERS:
            oci_java_image(
                name = "check_libharfbuzz_java" + java_version + "_" + user + "_" + distro,
                srcs = ["testdata/CheckLibharfbuzz.java"],
                base = "//java:java" + java_version + "_" + user + "_amd64_" + distro,
                main_class = "testdata.CheckLibharfbuzz",
            )

        for user in USERS:
            container_structure_test(
                name = "check_libharfbuzz_java" + java_version + "_" + user + "_" + distro + "_test",
                configs = ["testdata/java_libharfbuzz.yaml"],
                image = ":check_libharfbuzz_java" + java_version + "_" + user + "_" + distro,
                tags = [
                    "amd64",
                    "manual",
                ],
            )


================================================
FILE: java/jre_ver.bzl
================================================
def jre_ver(version):
    """Extract JRE version from a Debian openjdk downloaded filename.

      Debian packages versions are of the form:
         openjdk-11-jre*: 11.0.1+13-2~bpo9+1
    """
    if version.startswith("11.") or version.startswith("17.") or version.startswith("21.") or version.startswith("25."):
        v = version.split("+")[0].split(".")
        return v[0] + "." + v[1] + "." + v[2]

    fail("unrecognized openjdk package version: " + version)


================================================
FILE: java/testdata/CheckCerts.java
================================================
package testdata;

import java.io.IOException;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;

public class CheckCerts {

  public static void main(String[] args) throws IOException {
    URL url = new URL("https://www.google.com/");
    HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
    conn.connect();
    conn.getServerCertificates(); // succeeds if peer verified

    System.out.println("Successfully connected: " + conn.getResponseCode());
  }
}


================================================
FILE: java/testdata/CheckEncoding.java
================================================
package testdata;

import java.nio.charset.Charset;
import java.util.Locale;

public class CheckEncoding {

  public static void main(String[] args) {
    System.out.println("LANG=" + System.getenv("LANG"));
    System.out.println("Locale.getDefault()=" + Locale.getDefault());
    System.out.println("Charset.defaultCharset()=" + Charset.defaultCharset());
    System.out.println("file.encoding=" + System.getProperty("file.encoding"));
    System.out.println("sun.jnu.encoding=" + System.getProperty("sun.jnu.encoding"));
  }
}


================================================
FILE: java/testdata/CheckLibharfbuzz.java
================================================
package testdata;

import java.awt.GraphicsEnvironment;
import java.awt.Font;

public class CheckLibharfbuzz {
    public static void main(String[] args) {
        GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
        Font [] fonts = env.getAllFonts();
        if ( fonts.length > 0 ) {
            System.out.println(fonts.length + " fonts available");
        }
    }
}


================================================
FILE: java/testdata/java17_debian13.yaml
================================================
schemaVersion: "2.0.0"
commandTests:
  - name: java
    command: "/usr/lib/jvm/temurin-17-jre-amd64/bin/java"
    args: ["-version"]
    expectedError: ['openjdk version "17.0.18']
  - name: java-symlink
    command: "/usr/bin/java"
    args: ["-version"]
    expectedError: ['openjdk version "17.0.18']
fileExistenceTests:
  - name: certs
    path: "/etc/ssl/certs/java/cacerts"
    shouldExist: true
  - name: certs
    path: "/etc/ssl/certs/adoptium/cacerts"
    permissions: 'Lrwxrwxrwx'
    shouldExist: true
  - name: certs
    path: "/usr/lib/jvm/temurin-17-jre-amd64/lib/security/cacerts"
    permissions: 'Lrwxrwxrwx'
    shouldExist: true
  - name: no-busybox
    path: "/busybox/sh"
    shouldExist: false
  - name: no-shell
    path: "/bin/sh"
    shouldExist: false
  - name: no-javac
    path: "/usr/lib/jvm/temurin-17-jre-amd64/bin/javac"
    shouldExist: false
  - name: jexec-executable
    path: "/usr/lib/jvm/temurin-17-jre-amd64/lib/jexec"
    shouldExist: true
    isExecutableBy: "any"
  - name: jspawnhelper-executable
    path: "/usr/lib/jvm/temurin-17-jre-amd64/lib/jspawnhelper"
    shouldExist: true
    isExecutableBy: "any"
metadataTest:
  envVars:
    - key: 'JAVA_VERSION'
      value: '17.0.18'


================================================
FILE: java/testdata/java17_debug_debian13.yaml
================================================
schemaVersion: "2.0.0"
commandTests:
  - name: java
    command: "/usr/lib/jvm/temurin-17-jdk-amd64/bin/java"
    args: ["-version"]
    expectedError: ['openjdk version "17.0.18"']
  - name: java-symlink
    command: "/usr/bin/java"
    args: ["-version"]
    expectedError: ['openjdk version "17.0.18"']
  - name: javac
    command: "/usr/lib/jvm/temurin-17-jdk-amd64/bin/javac"
    args: ["-version"]
    expectedOutput: ['javac 17.0.18']
fileExistenceTests:
  - name: certs
    path: "/etc/ssl/certs/java/cacerts"
    shouldExist: true
  - name: certs
    path: "/etc/ssl/certs/adoptium/cacerts"
    permissions: 'Lrwxrwxrwx'
    shouldExist: true
  - name: certs
    path: "/usr/lib/jvm/temurin-17-jdk-amd64/lib/security/cacerts"
    permissions: 'Lrwxrwxrwx'
    shouldExist: true
  - name: busybox
    path: "/busybox/sh"
    shouldExist: true
  - name: no-shell
    path: "/bin/sh"
    shouldExist: false
metadataTest:
  envVars:
    - key: 'JAVA_VERSION'
      value: '17.0.18'


================================================
FILE: java/testdata/java21_debian13.yaml
================================================
schemaVersion: "2.0.0"
commandTests:
  - name: java
    command: "/usr/lib/jvm/temurin-21-jre-amd64/bin/java"
    args: ["-version"]
    expectedError: ['openjdk version "21.0.10"']
  - name: java-symlink
    command: "/usr/bin/java"
    args: ["-version"]
    expectedError: ['openjdk version "21.0.10"']
fileExistenceTests:
  - name: certs
    path: "/etc/ssl/certs/java/cacerts"
    shouldExist: true
  - name: certs
    path: "/etc/ssl/certs/adoptium/cacerts"
    permissions: 'Lrwxrwxrwx'
    shouldExist: true
  - name: certs
    path: "/usr/lib/jvm/temurin-21-jre-amd64/lib/security/cacerts"
    permissions: 'Lrwxrwxrwx'
    shouldExist: true
  - name: no-busybox
    path: "/busybox/sh"
    shouldExist: false
  - name: no-shell
    path: "/bin/sh"
    shouldExist: false
  - name: no-javac
    path: "/usr/lib/jvm/temurin-21-jre-amd64/bin/javac"
    shouldExist: false
  - name: jexec-executable
    path: "/usr/lib/jvm/temurin-21-jre-amd64/lib/jexec"
    shouldExist: true
    isExecutableBy: "any"
  - name: jspawnhelper-executable
    path: "/usr/lib/jvm/temurin-21-jre-amd64/lib/jspawnhelper"
    shouldExist: true
    isExecutableBy: "any"
metadataTest:
  envVars:
    - key: 'JAVA_VERSION'
      value: '21.0.10'


================================================
FILE: java/testdata/java21_debug_debian13.yaml
================================================
schemaVersion: "2.0.0"
commandTests:
  - name: java
    command: "/usr/lib/jvm/temurin-21-jdk-amd64/bin/java"
    args: ["-version"]
    expectedError: ['openjdk version "21.0.10"']
  - name: java-symlink
    command: "/usr/bin/java"
    args: ["-version"]
    expectedError: ['openjdk version "21.0.10"']
  - name: javac
    command: "/usr/lib/jvm/temurin-21-jdk-amd64/bin/javac"
    args: ["-version"]
    expectedOutput: ['javac 21.0.10']
fileExistenceTests:
  - name: certs
    path: "/etc/ssl/certs/java/cacerts"
    shouldExist: true
  - name: certs
    path: "/etc/ssl/certs/adoptium/cacerts"
    permissions: 'Lrwxrwxrwx'
    shouldExist: true
  - name: certs
    path: "/usr/lib/jvm/temurin-21-jdk-amd64/lib/security/cacerts"
    permissions: 'Lrwxrwxrwx'
    shouldExist: true
  - name: busybox
    path: "/busybox/sh"
    shouldExist: true
  - name: no-shell
    path: "/bin/sh"
    shouldExist: false
metadataTest:
  envVars:
    - key: 'JAVA_VERSION'
      value: '21.0.10'


================================================
FILE: java/testdata/java25_debian13.yaml
================================================
schemaVersion: "2.0.0"
commandTests:
  - name: java
    command: "/usr/lib/jvm/temurin-25-jre-amd64/bin/java"
    args: ["-version"]
    expectedError: ['openjdk version "25.0.2"']
  - name: java-symlink
    command: "/usr/bin/java"
    args: ["-version"]
    expectedError: ['openjdk version "25.0.2"']
fileExistenceTests:
  - name: certs
    path: "/etc/ssl/certs/java/cacerts"
    shouldExist: true
  - name: certs
    path: "/etc/ssl/certs/adoptium/cacerts"
    permissions: 'Lrwxrwxrwx'
    shouldExist: true
  - name: certs
    path: "/usr/lib/jvm/temurin-25-jre-amd64/lib/security/cacerts"
    permissions: 'Lrwxrwxrwx'
    shouldExist: true
  - name: no-busybox
    path: "/busybox/sh"
    shouldExist: false
  - name: no-shell
    path: "/bin/sh"
    shouldExist: false
  - name: no-javac
    path: "/usr/lib/jvm/temurin-25-jre-amd64/bin/javac"
    shouldExist: false
  - name: jexec-executable
    path: "/usr/lib/jvm/temurin-25-jre-amd64/lib/jexec"
    shouldExist: true
    isExecutableBy: "any"
  - name: jspawnhelper-executable
    path: "/usr/lib/jvm/temurin-25-jre-amd64/lib/jspawnhelper"
    shouldExist: true
    isExecutableBy: "any"
metadataTest:
  envVars:
    - key: 'JAVA_VERSION'
      value: '25.0.2'


================================================
FILE: java/testdata/java25_debug_debian13.yaml
================================================
schemaVersion: "2.0.0"
commandTests:
  - name: java
    command: "/usr/lib/jvm/temurin-25-jdk-amd64/bin/java"
    args: ["-version"]
    expectedError: ['openjdk version "25.0.2"']
  - name: java-symlink
    command: "/usr/bin/java"
    args: ["-version"]
    expectedError: ['openjdk version "25.0.2"']
  - name: javac
    command: "/usr/lib/jvm/temurin-25-jdk-amd64/bin/javac"
    args: ["-version"]
    expectedOutput: ['javac 25.0.2']
fileExistenceTests:
  - name: certs
    path: "/etc/ssl/certs/java/cacerts"
    shouldExist: true
  - name: certs
    path: "/etc/ssl/certs/adoptium/cacerts"
    permissions: 'Lrwxrwxrwx'
    shouldExist: true
  - name: certs
    path: "/usr/lib/jvm/temurin-25-jdk-amd64/lib/security/cacerts"
    permissions: 'Lrwxrwxrwx'
    shouldExist: true
  - name: busybox
    path: "/busybox/sh"
    shouldExist: true
  - name: no-shell
    path: "/bin/sh"
    shouldExist: false
metadataTest:
  envVars:
    - key: 'JAVA_VERSION'
      value: '25.0.2'


================================================
FILE: java/testdata/java_base.yaml
================================================
schemaVersion: "2.0.0"
fileExistenceTests:
  - name: certs
    path: "/etc/ssl/certs/java/cacerts"
    shouldExist: true
  - name: no-busybox
    path: "/busybox/sh"
    shouldExist: false
  - name: no-shell
    path: "/bin/sh"
    shouldExist: false
  - name: no-jvm
    path: "/usr/lib/jvm"
    shouldExist: false


================================================
FILE: java/testdata/java_base_debug.yaml
================================================
schemaVersion: "2.0.0"
fileExistenceTests:
  - name: certs
    path: "/etc/ssl/certs/java/cacerts"
    shouldExist: true
  - name: busybox
    path: "/busybox/sh"
    shouldExist: true
  - name: no-shell
    path: "/bin/sh"
    shouldExist: false
  - name: no-jvm
    path: "/usr/lib/jvm"
    shouldExist: false


================================================
FILE: java/testdata/java_certs.yaml
================================================
schemaVersion: "1.0.0"
commandTests:
  - name: connect_to_https_google_com
    # This is a bit ugly because structure tests can't test the default entrypoint yet.
    command: ["/usr/bin/java",
              "-cp",
              "/*",
              "testdata.CheckCerts"]
    expectedOutput: ['Successfully connected: 200']


================================================
FILE: java/testdata/java_encoding.yaml
================================================
schemaVersion: "1.0.0"
commandTests:
  - name: check_encoding
    command: ["/usr/bin/java",
              "-cp",
              "/*",
              "testdata.CheckEncoding"]
    expectedOutput: ['LANG=C.UTF-8',
                     'Locale.getDefault\(\)=en',
                     'Charset.defaultCharset\(\)=UTF-8',
                     'file.encoding=UTF-8',
                     'sun.jnu.encoding=UTF-8']


================================================
FILE: java/testdata/java_libharfbuzz.yaml
================================================
schemaVersion: "1.0.0"
commandTests:
  - name: check_libharfbuzz
    command: ["/usr/bin/java",
              "-cp",
              "/*",
              "testdata.CheckLibharfbuzz"]
    expectedOutput: ['^\d+ fonts available']


================================================
FILE: knife
================================================
#!/usr/bin/env bash
set -o pipefail -o errexit -o nounset

# Copyright 2024 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.

source knife.d/update_java_versions.sh

if [ $(uname) == "Darwin" ]; then
    echo "WARNING: You are on a macos, you need to run 'brew install coreutils gnu-sed' to install required packages."
    echo ""
    export PATH="/opt/homebrew/opt/coreutils/libexec/gnubin:$PATH"
    export PATH="/opt/homebrew/opt/gnu-sed/libexec/gnubin:$PATH"
fi

function cmd_lock_all() {
    cmd_lock_snapshots
    cmd_lock_non_snapshots
}

function cmd_lock_snapshots() {
    echo "🚧 Querying for snapshot repos"
    echo ""
    local repos=$(grep -l snapshot.debian.org ./private/repos/deb/*.yaml | xargs -L 1 basename | cut -d. -f 1)
    _cmd_lock "$repos"
}

function cmd_lock_non_snapshots() {
    echo "🚧 Querying for non_snapshot repos"
    echo ""
    local repos=$(grep -lL snapshot.debian.org ./private/repos/deb/*.yaml | xargs -L 1 basename | cut -d. -f 1)
    _cmd_lock "$repos"
    update_java_versions_debian13
}

function _cmd_lock() {
    local repos="$1"
    for repo in $repos; do
      for i in $(seq 10); do
        echo "🔑 Locking $repo (attempt ${i})"
        bazel run "@${repo}//:lock" && break || sleep 20;
        if [[ $i -eq 10 ]]; then
          echo ""
          echo "Failed to lock $repo after 10 attempts" >&2
          exit 1
        fi
      done
    done
}

function find_latest_snapshot() {
    local type="$1"
    # If it's the first of the month, look at the last month, otherwise this -1 day has no effect since searches
    # occur at the "month" level. This is an intentional buffer added to get the snapshots fully hydrated. We
    # intentionally don't include complicated logic for the case where it's after the 1st and no snapshots are
    # availalbe for the month (it's extremely unlikely for our updater to run into this situation unless the
    # snapshot serving infrastructure is acting up).
    local current="$(date -d '-1 day' +%Y-%m-%d)"
    local tmp=$(mktemp)
    local q=$(date -d "$current" +"year=%Y&month=%m")
    if curl -fs "https://snapshot.debian.org/archive/debian/?$q" | grep -ohE "([0-9]+T[0-9]+Z)" > $tmp; then
      # same logic as above, find the newest snapshot that isn't "today"
      today=$(date +"%Y%m%dT")
      cat $tmp | grep -v $today | tail -n1
    fi
}

function cmd_update_snapshots() {
    echo "🧐 Looking for updates... "
    latest=$(find_latest_snapshot "debian")
    latest_security=$(find_latest_snapshot "debian-security")
    if [[ -z "$latest" || -z "$latest_security" ]]; then
        echo ""
        echo "could not find any snapshots for debian or debian-security"
        exit 1
    fi
    echo ""
    echo "🎯 Found snapshots"
    echo "   debian: $latest"
    echo "   security: $latest_security"
    echo ""

    # if tty ask for approval
    if [ -t 1 ]; then
        read -p "Do you want to continue? (y/n) " -n 1 -r
        sleep 0.5
        echo $'\n'
        if [[ ! "$REPLY" =~ ^[Yy]$ ]]; then
            echo "Aborting..."
            exit 0
        fi
    fi

    for mpath in "./private/repos/deb/"*.yaml; do
        if ! grep -q "snapshot.debian.org" "$mpath"; then
            echo "ignoring non-snapshot repo $mpath"
            continue
        fi

        current=$(grep -oE "debian/([0-9]+T[0-9]+Z)" $mpath | cut -d/ -f2 | head -n1)
        current_security=$(grep -oE "debian-security/([0-9]+T[0-9]+Z)" $mpath | cut -d/ -f2 | head -n1)

        if [[ "$current" == "$latest" && "$current_security" == "$latest_security" ]]; then
            echo "🎖️ $mpath is up to date."
            continue
        fi
        echo "🗞️ $mpath"
        if [[ "$current" != "$latest" ]]; then
            sed -i -E "s/(debian\/)([0-9]+T[0-9]+Z)/\1$latest/" "$mpath"
            echo "   debian: $current -> $latest"
        fi
        if [[ "$current_security" != "$latest_security" ]]; then
            sed -i -E "s/(debian-security\/)([0-9]+T[0-9]+Z)/\1$latest_security/" "$mpath"
            echo "   debian-security: $current_security -> $latest_security"
        fi
        echo ""
    done
    echo ""
    echo "👌 Done..."
}

# write DISTROLESS_DIFF to GITHUB_ENV if changes are made
function cmd_github_update_snapshots() {
    local tmp=$(mktemp -d)
    jq -nr 'inputs.packages[] | .key + " " + .sha256' ./private/repos/deb/*.lock.json | sort > "$tmp/old.hashes"
    cmd_update_snapshots
    cmd_lock_snapshots
    jq -nr 'inputs.packages[] | .key + " " + .sha256' ./private/repos/deb/*.lock.json | sort > "$tmp/new.hashes"
    diff "$tmp/old.hashes" "$tmp/new.hashes" | tee "$tmp/diff" || printf "DISTROLESS_DIFF<<EOF\n$(<$tmp/diff)\nEOF" >> "$GITHUB_ENV"
}

function cmd_lint () {
    local mode="fix"
    if [[ "${1:-}" == "--check" ]]; then
        mode="check"
    fi
    echo "🧹 Linting"
    echo ""
    if ! which buildifier > /dev/null; then
        echo "🧱 No buildifier executable was found."
        echo " Did you follow the ./CONTRIBUTING.md ?"
        exit 1
    fi
    buildifier -mode=$mode $(find . -type f \( -name 'BUILD*' -o -name 'WORKSPACE*' -o -name '*.bzl' \))
}

function cmd_update_node_archives () {
	if ! which jq > /dev/null; then
		echo "🧱 No jq executable was found"
		exit 1
    fi
	if ! which curl > /dev/null; then
		echo "🧱 No curl executable was found"
		exit 1
	fi
	if ! which node > /dev/null; then
		echo "🧱 No node executable was found"
		exit 1
	fi

    versions=()
    for major in 20 22 24; do
        latest_version=$(curl -sSL https://nodejs.org/dist/index.json | jq -r --arg major "v$major" '
            map(select(.version | startswith($major)))
            | sort_by(.date) | reverse | .[0].version
        ')
        latest_nov=$(echo "$latest_version" | sed 's/^v//')
        versions+=("$latest_nov")
    done

    joined_versions=$(IFS=, ; echo "${versions[*]}")
    node knife.d/update_node_archives.js "$joined_versions"
}


function cmd_test () {
    echo "🧪 Testing"
    echo ""

    local arch=$(uname -m)
    if [ ${arch} == "x86_64" ]; then
      arch="amd64"
    fi

    echo "💡 only including image tests for $arch"
    echo ""

    arch_specific_targets=$(bazel query "attr(\"tags\", "$arch", \"//...\")")

    # Run all tests tagged with "amd64"
    bazel test --test_timeout=900 //... $arch_specific_targets
}

function cmd_deb_versions () {
    echo "🔧 Printing .deb Versions (bookworm) from private/repos/deb/bookworm*.lock.json"
    echo ""

    jq -n '[inputs.packages[]] | group_by(.arch) | map({(.[0].arch): map({package: .name, version: .version})})' private/repos/deb/bookworm*.lock.json
}

case "${1:-"~~nocmd"}" in
lock)
    cmd_lock_all
    ;;
update-snapshots)
    cmd_update_snapshots
    ;;
update-non-snapshots)
    cmd_lock_non_snapshots
    ;;
lint)
    cmd_lint "${@:2}"
    ;;
github-update-snapshots)
    cmd_github_update_snapshots
    ;;
test)
    cmd_test
    ;;
deb-versions)
    cmd_deb_versions
    ;;
update-node-archives)
	cmd_update_node_archives
	;;
~~nocmd) # no command provided
    echo "provide a command: lock, update-snapshots, github-update-snapshots, update-non-snapshots, test, deb-versions, update-node-archives"
    exit 1
    ;;
*) # unknown command
    echo "unknown command $1"
    exit 1
    ;;
esac


================================================
FILE: knife.d/update_java_versions.sh
================================================
set -o pipefail -o errexit -o nounset

# Copyright 2024 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.

# a collection of functions to use when updating java archives from the knife utility


function update_java_versions_debian13() {
  local java_versions=("17" "21" "25")

  for java_version in "${java_versions[@]}"; do
    local version=$(jq -r --arg jv "temurin-${java_version}-jre" '.packages.[] | select((.arch=="amd64") and (.name==$jv)) | .version | split(".") | .[0:3] | join(".")' private/repos/deb/trixie_adoptium.lock.json)
    local major_version=$(echo "$version" | cut -d. -f 1)
    sed -i -r -e "s/${major_version}\\.[0-9]+\\.[0-9]+/${version}/g" java/testdata/java${major_version}*debian13.yaml
  done
}


================================================
FILE: knife.d/update_node_archives.js
================================================
#!/usr/bin/env node
const crypto = require("crypto");
const https = require("https");
const fs = require("fs");

if (process.argv.length < 3) {
  console.error("Usage: node nodeChecksum.js <nodejs_version>");
  process.exit(1);
}

const versions = process.argv[2].split(",");
const architectures = ["amd64", "arm64", "arm", "ppc64le", "s390x"];

const nodeVersions = {};

const calculateChecksum = (url) => {
  return new Promise((resolve, reject) => {
    https
      .get(url, (res) => {
        const hash = crypto.createHash("sha256");
        res.on("data", (data) => {
          hash.update(data);
        });
        res.on("end", () => {
          resolve(hash.digest("hex"));
        });
      })
      .on("error", (err) => {
        reject(`Error downloading file: ${err.message}`);
      });
  });
};

const fetchChecksums = async () => {
  for (const nodeVersion of versions) {
    const major = parseInt(nodeVersion.split(".")[0]);
    nodeVersions[nodeVersion] = {};
    await Promise.all(
      architectures.map(async (key) => {
        let arch = key;
        if (major > 22 && key === "arm") {
          return;
        }
        if (key === "amd64") {
          arch = "x64";
        } else if (key === "arm") {
          arch = "armv7l";
        }
        const url = `https://nodejs.org/dist/v${nodeVersion}/node-v${nodeVersion}-linux-${arch}.tar.gz`;
        try {
          const checksum = await calculateChecksum(url);
          nodeVersions[nodeVersion][key] = {
            checksum,
            suffix: arch,
          };
        } catch (error) {
          console.error(error);
        }
      })
    );
  }
};

fetchChecksums().then(() => {
  let nodeArchives = `"node"

BUILD_TMPL = """\\
# GENERATED BY node_archive.bzl
load("@distroless//private/pkg:debian_spdx.bzl", "debian_spdx")
load("@distroless//private/util:merge_providers.bzl", "merge_providers")
load("@distroless//private/util:tar.bzl", "tar")

tar(
    name = "data",
    extension = "tar.gz",
    srcs = glob(
        [
            "output/bin/node",
            "output/LICENSE",
        ],
    ),
    package_dir = "/nodejs",
    strip_prefix = "external/{canonical_name}/output"
)

tar(
    name = "_control",
    extension = "tar.gz",
    srcs = ["control"]
)

debian_spdx(
    name = "spdx",
    control = ":_control",
    data = ":data",
    package_name = "{package_name}",
    spdx_id = "{spdx_id}",
    sha256 = "{sha256}",
    urls = [{urls}]
)

merge_providers(
    name = "{name}",
    srcs = [":data", ":spdx"],
    visibility = ["//visibility:public"],
)
"""

def _impl(rctx):
    rctx.report_progress("Fetching {}".format(rctx.attr.package_name))
    rctx.download_and_extract(
        url = rctx.attr.urls,
        sha256 = rctx.attr.sha256,
        type = rctx.attr.type,
        stripPrefix = rctx.attr.strip_prefix,
        output = "output",
    )
    rctx.template(
        "control",
        rctx.attr.control,
        substitutions = {
            "{{VERSION}}": rctx.attr.version,
            "{{ARCHITECTURE}}": rctx.attr.architecture,
            "{{SHA256}}": rctx.attr.sha256,
        },
    )
    rctx.file(
        "BUILD.bazel",
        content = BUILD_TMPL.format(
            canonical_name = rctx.attr.name,
            name = rctx.attr.name.split("~")[-1],
            package_name = rctx.attr.package_name,
            spdx_id = rctx.attr.name,
            urls = ",".join(['"%s"' % url for url in rctx.attr.urls]),
            sha256 = rctx.attr.sha256,
        ),
    )

node_archive = repository_rule(
    implementation = _impl,
    attrs = {
        "urls": attr.string_list(mandatory = True),
        "sha256": attr.string(mandatory = True),
        "type": attr.string(default = ".tar.gz"),
        "strip_prefix": attr.string(),
        "package_name": attr.string(default = "nodejs"),
        "version": attr.string(mandatory = True),
        "architecture": attr.string(mandatory = True),
        # control is only used to populate the sbom, see https://github.com/GoogleContainerTools/distroless/issues/1373
        # for why writing debian control files to the image is incompatible with scanners.
        "control": attr.label(),
    },
)

def _node_impl(module_ctx):
    mod = module_ctx.modules[0]

    if len(module_ctx.modules) > 1:
        fail("node.archive should be called only once")
    if not mod.is_root:
        fail("node.archive should be called from root module only.")

    # Node (https://nodejs.org/en/about/releases/)
    # Follow Node's maintainence schedule and support all LTS versions that are not end of life`;

  for (const nodeVersion of versions) {
    const major = parseInt(nodeVersion.split(".")[0]);

    for (const key of architectures) {
      if (major > 22 && key === "arm") {
        continue;
      }
      const arch = nodeVersions[nodeVersion][key];
      const url = `https://nodejs.org/dist/v${nodeVersion}/node-v${nodeVersion}-linux-${arch.suffix}.tar.gz`;

      nodeArchives += "\n";
      nodeArchives += `
    node_archive(
        name = "nodejs${major}_${key}",
        sha256 = "${arch.checksum}",
        strip_prefix = "node-v${nodeVersion}-linux-${arch.suffix}/",
        urls = ["${url}"],
        version = "${nodeVersion}",
        architecture = "${key}",
        control = "//nodejs:control",
    )`;
    }

    const testData = `schemaVersion: "2.0.0"
commandTests:
  - name: nodejs
    command: "/nodejs/bin/node"
    args: ["--version"]
    expectedOutput: ["v${nodeVersion}"]
`;

    fs.writeFile(`nodejs/testdata/nodejs${major}.yaml`, testData, (err) => {
      if (err) {
        console.error(err);
      }
    });
  }
  nodeArchives += `

    return module_ctx.extension_metadata(
        root_module_direct_deps = [`;

  for (const nodeVersion of versions) {
    const major = parseInt(nodeVersion.split(".")[0]);
    for (const arch of architectures) {
      if (major > 22 && arch === "arm") {
        continue;
      }
      nodeArchives += `
            "nodejs${major}_${arch}",`;
    }
  }

  nodeArchives += `
        ],
        root_module_direct_dev_deps = [],
    )

_archive = tag_class(attrs = {})

node = module_extension(
    implementation = _node_impl,
    tag_classes = {
        "archive": _archive,
    },
)
`;

  // Write output to node_archives.bzl file
  fs.writeFile("private/extensions/node.bzl", nodeArchives, (err) => {
    if (err) {
      console.error(err);
    }
  });
});


================================================
FILE: nodejs/BUILD
================================================
load(":config.bzl", "NODEJS_ARCHITECTURES", "NODEJS_DISTROS", "NODEJS_MAJOR_VERSIONS")
load(":nodejs.bzl", "nodejs_image", "nodejs_image_index")

package(default_visibility = ["//visibility:public"])

[
    nodejs_image(
        arch = arch,
        distro = distro,
        major_version = major_version,
    )
    for distro in NODEJS_DISTROS
    for major_version in NODEJS_MAJOR_VERSIONS
    for arch in NODEJS_ARCHITECTURES[distro][major_version]
]

[
    nodejs_image_index(
        architectures = NODEJS_ARCHITECTURES[distro][major_version],
        distro = distro,
        major_version = major_version,
    )
    for distro in NODEJS_DISTROS
    for major_version in NODEJS_MAJOR_VERSIONS
]


================================================
FILE: nodejs/README.md
================================================
# Documentation for `gcr.io/distroless/nodejs`

## Image Contents

These images contain a minimal Linux, Node.js-based runtime. The supported versions match the [Node.js LTS releases](https://nodejs.org/en/about/previous-releases).

Specifically, these images contain everything in the [cc image](../cc/README.md), plus one of:

- Node.js v20 (`gcr.io/distroless/nodejs20-debian13`) and its dependencies.
- Node.js v22 (`gcr.io/distroless/nodejs22-debian13`) and its dependencies.
- Node.js v24 (`gcr.io/distroless/nodejs24-debian13`) and its dependencies.

## Usage

The entrypoint of this image is set to "node", so this image expects users to supply a path to a .js file in the CMD.

See the Node.js [Hello World](../examples/nodejs/) directory for an example.


================================================
FILE: nodejs/config.bzl
================================================
NODEJS_DISTROS = ["debian13"]
NODEJS_ARCHITECTURES = {
    "debian13": {
        "20": ["amd64", "arm64", "arm", "s390x", "ppc64le"],
        "22": ["amd64", "arm64", "arm", "s390x", "ppc64le"],
        "24": ["amd64", "arm64", "s390x", "ppc64le"],
    },
}
NODEJS_MAJOR_VERSIONS = ["20", "22", "24"]


================================================
FILE: nodejs/control
================================================
Package: nodejs
Version: {{VERSION}}
Architecture: {{ARCHITECTURE}}
Maintainer: OpenJS Foundation <https://openjsf.org/>
Homepage: https://nodejs.org
SHA256: {{SHA256}}
Description: Node.js event-based server-side javascript engine
 Node.js is similar in design to and influenced by systems like
 Ruby's Event Machine or Python's Twisted.
 .
 It takes the event model a bit further - it presents the event
 loop as a language construct instead of as a library.
 .
 Node.js is bundled with several useful libraries to handle server tasks :
 System, Events, Standard I/O, Modules, Timers, Child Processes, POSIX,
 HTTP, Multipart Parsing, TCP, DNS, Assert, Path, URL, Query Strings.


================================================
FILE: nodejs/nodejs.bzl
================================================
"nodejs image definitions"

load("@container_structure_test//:defs.bzl", "container_structure_test")
load("@rules_oci//oci:defs.bzl", "oci_image", "oci_image_index")
load("//common:variables.bzl", "DEBUG_MODE", "USERS")
load("//private/util:tar.bzl", "tar")

def nodejs_image_index(distro, major_version, architectures):
    """nodejs image index for a distro.

    Args:
        distro: name of distribution
        major_version: version of nodejs
        architectures: all architectures included in index
    """
    for mode in DEBUG_MODE:
        for user in USERS:
            oci_image_index(
                name = "nodejs" + major_version + mode + "_" + user + "_" + distro,
                images = [
                    "nodejs" + major_version + mode + "_" + user + "_" + arch + "_" + distro
                    for arch in architectures
                ],
            )

def _check_certificates_tar():
    # only create once
    if native.existing_rule("check_certificate"):
        return

    tar(
        name = "check_certificate",
        extension = "tar.gz",
        srcs = ["testdata/check_certificate.js"],
    )

def nodejs_image(distro, major_version, arch):
    """nodejs and debug image with tests.

    Args:
        distro: name of distribution
        major_version: version of nodejs
        arch: the target arch
    """
    for mode in DEBUG_MODE:
        for user in USERS:
            oci_image(
                name = "nodejs" + major_version + mode + "_" + user + "_" + arch + "_" + distro,
                base = "//cc:cc" + mode + "_" + user + "_" + arch + "_" + distro,
                entrypoint = ["/nodejs/bin/node"],
                tars = [
                    "@nodejs" + major_version + "_" + arch,
                ],
            )

    _check_certificates_tar()

    for mode in DEBUG_MODE:
        for user in USERS:
            container_structure_test(
                name = "nodejs" + major_version + mode + "_" + user + "_" + arch + "_" + distro + "_test",
                configs = [
                    "testdata/nodejs" + major_version + ".yaml",
                    "testdata/check_headers.yaml",
                    "testdata/check_npm.yaml",
                ],
                image = "nodejs" + major_version + mode + "_" + user + "_" + arch + "_" + distro,
                tags = [
                    arch,
                    "manual",
                ],
            )

    for mode in DEBUG_MODE:
        for user in USERS:
            oci_image(
                name = "check_certificate_nodejs" + major_version + mode + "_" + user + "_" + arch + "_" + distro,
                base = "nodejs" + major_version + mode + "_" + user + "_" + arch + "_" + distro,
                tars = [
                    ":check_certificate",
                ],
            )

    for mode in DEBUG_MODE:
        for user in USERS:
            container_structure_test(
                name = "check_certificate_nodejs" + major_version + mode + "_" + user + "_" + arch + "_" + distro + "_test",
                configs = ["testdata/check_certificate.yaml"],
                image = "check_certificate_nodejs" + major_version + mode + "_" + user + "_" + arch + "_" + distro,
                tags = [
                    arch,
                    "manual",
                ],
            )


================================================
FILE: nodejs/testdata/check_certificate.js
================================================
const https = require('https')
const options = {
  hostname: 'www.google.com',
  port: 443,
  path: '/',
  method: 'GET'
}

const req = https.request(options, res => {
  console.log(`statusCode: ${res.statusCode}`)
  res.resume()
})

req.end()


================================================
FILE: nodejs/testdata/check_certificate.yaml
================================================
schemaVersion: "2.0.0"
commandTests:
  - name: nodejs
    command: "/nodejs/bin/node" 
    args: ["/check_certificate.js"]
    expectedOutput: ['statusCode: 200']


================================================
FILE: nodejs/testdata/check_headers.yaml
================================================
schemaVersion: "2.0.0"
fileExistenceTests:
  - name: npm
    path: '/nodejs/include/node/node.h'
    shouldExist: false


================================================
FILE: nodejs/testdata/check_npm.yaml
================================================
schemaVersion: "2.0.0"
fileExistenceTests:
  - name: npm
    path: '/nodejs/lib/node_modules/npm'
    shouldExist: false
  - name: corepack
    path: '/nodejs/lib/node_modules/corepack'
    shouldExist: false
  - name: npm
    path: '/nodejs/bin/npm'
    shouldExist: false
  - name: corepack
    path: '/nodejs/bin/corepack'
    shouldExist: false
  - name: npx
    path: '/nodejs/bin/npx'
    shouldExist: false


================================================
FILE: nodejs/testdata/nodejs20.yaml
================================================
schemaVersion: "2.0.0"
commandTests:
  - name: nodejs
    command: "/nodejs/bin/node"
    args: ["--version"]
    expectedOutput: ["v20.20.1"]


================================================
FILE: nodejs/testdata/nodejs22.yaml
================================================
schemaVersion: "2.0.0"
commandTests:
  - name: nodejs
    command: "/nodejs/bin/node"
    args: ["--version"]
    expectedOutput: ["v22.22.1"]


================================================
FILE: nodejs/testdata/nodejs24.yaml
================================================
schemaVersion: "2.0.0"
commandTests:
  - name: nodejs
    command: "/nodejs/bin/node"
    args: ["--version"]
    expectedOutput: ["v24.14.0"]


================================================
FILE: private/extensions/BUILD.bazel
================================================


================================================
FILE: private/extensions/busybox.bzl
================================================
"busybox"

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file")
load("//experimental/busybox:commands.bzl", "BUSYBOX_ARCHIVE_BUILD")

def _busybox_impl(module_ctx):
    mod = module_ctx.modules[0]

    if len(module_ctx.modules) > 1:
        fail("busybox.archive should be called only once")
    if not mod.is_root:
        fail("busybox.archive should be called from root module only.")

    # To update amd64 busybox binary (#1014)
    # Get the latest commit hash from dist-amd64 branch of docker-library repo. You can also view it
    # at https://github.com/docker-library/official-images/blob/master/library/busybox
    # Substitute it in the link: https://github.com/docker-library/busybox/raw/<latest-commit-hash>/latest/musl/busybox.tar.gz
    # Update the sha256 value. Since github api doesn't give sha256 value, it can be obtained using sha256sum command.
    http_archive(
        name = "busybox_amd64",
        sha256 = "8e7bef4a92afca21c56c9f85e4e63885b320f1da8f8f82a1cf87af359faf57d3",
        urls = ["https://raw.githubusercontent.com/docker-library/busybox/e5b5178110ca0332364a77d5eb4fff87b0e2ba3f/latest/musl/amd64/rootfs.tar.gz"],
        build_file_content = BUSYBOX_ARCHIVE_BUILD,
    )

    # To update arm busybox binary
    # Get the latest commit hash from dist-arm32v7 branch of docker-library repo. You can also view it
    # at https://github.com/docker-library/official-images/blob/master/library/busybox
    # Substitute it in the link: https://github.com/docker-library/busybox/raw/<latest-commit-hash>/latest/musl/arm32v7/rootfs.tar.gz
    # Update the sha256 value. Since github api doesn't give sha256 value, it can be obtained using sha256sum command.
    http_archive(
        name = "busybox_arm",
        sha256 = "3c873527f998bba56151b7c7d19c133390a44ef894242e7ad5536de1ac0c347e",
        urls = ["https://raw.githubusercontent.com/docker-library/busybox/a4fac83861d137e9dcfce70b31d0b8fafea9346e/latest/musl/arm32v7/rootfs.tar.gz"],
        build_file_content = BUSYBOX_ARCHIVE_BUILD,
    )

    # To update arm64 busybox binary (#657)
    # Get the latest commit hash from dist-arm64v8 branch of docker-library repo. You can also view it
    # at https://github.com/docker-library/official-images/blob/master/library/busybox
    # Substitute it in the link: https://github.com/docker-library/busybox/raw/<latest-commit-hash>/latest/musl/busybox.tar.gz
    # Update the sha256 value. Since github api doesn't give sha256 value, it can be obtained using sha256sum command.
    http_archive(
        name = "busybox_arm64",
        sha256 = "07b73337cea2a5d87c21278f251e1e742e253232ebd035d6a4a6d2cb4612e462",
        urls = ["https://raw.githubusercontent.com/docker-library/busybox/f30de561de9bf38da10058620e9c4c383ec8a905/latest/musl/arm64v8/rootfs.tar.gz"],
        build_file_content = BUSYBOX_ARCHIVE_BUILD,
    )

    # To update s390x busybox binary
    # Get the latest commit hash from dist-s390x branch of docker-library repo. You can also view it
    # at https://github.com/docker-library/official-images/blob/master/library/busybox
    # Substitute it in the link: https://github.com/docker-library/busybox/raw/<latest-commit-hash>/latest/musl/s390x/rootfs.tar.gz
    # Update the sha256 value. Since github api doesn't give sha256 value, it can be obtained using sha256sum command.
    http_archive(
        name = "busybox_s390x",
        sha256 = "3bd977f75e22a5e164e2e6ac3556cee55053022e9ad8bba89614e7a8680791d1",
        urls = ["https://raw.githubusercontent.com/docker-library/busybox/87c948a4fac6f84195e795486a8d650b0c9cc10d/latest/musl/s390x/rootfs.tar.gz"],
        build_file_content = BUSYBOX_ARCHIVE_BUILD,
    )

    # To update ppc64le busybox binary (#723)
    # Get the latest commit hash from dist-ppc64le branch of docker-library repo. You can also view it
    # at https://github.com/docker-library/official-images/blob/master/library/busybox
    # Substitute it in the link: https://github.com/docker-library/busybox/raw/<latest-commit-hash>/latest/musl/busybox.tar.gz
    # Update the sha256 value. Since github api doesn't give sha256 value, it can be obtained using sha256sum command.
    http_archive(
        name = "busybox_ppc64le",
        sha256 = "f3f5d636887b8d56aabe29047a58ef074511f5252c722b1e476b7f79fd35fb7f",
        urls = ["https://raw.githubusercontent.com/docker-library/busybox/149c39d6036d77a55679c6b9e0c946ba2ad38555/latest/musl/ppc64le/rootfs.tar.gz"],
        build_file_content = BUSYBOX_ARCHIVE_BUILD,
    )

    # To update riscv64 busybox binary
    # Get the latest commit hash from dist-riscv64 branch of docker-library repo. You can also view it
    # at https://github.com/docker-library/official-images/blob/master/library/busybox
    # Substitute it in the link: https://github.com/docker-library/busybox/raw/<latest-commit-hash>/latest/musl/busybox.tar.gz
    # Update the sha256 value. Since github api doesn't give sha256 value, it can be obtained using sha256sum command.
    http_archive(
        name = "busybox_riscv64",
        sha256 = "17737180c892fb9eac75524ae789040f0b01415e6e05e063bc2b8f982b340d97",
        urls = ["https://raw.githubusercontent.com/docker-library/busybox/eabe7cf678f2abbc0477a8603428519771ede877/latest/musl/riscv64/rootfs.tar.gz"],
        build_file_content = BUSYBOX_ARCHIVE_BUILD,
    )

    return module_ctx.extension_metadata(
        root_module_direct_deps = [
            "busybox_amd64",
            "busybox_arm",
            "busybox_arm64",
            "busybox_s390x",
            "busybox_ppc64le",
            "busybox_riscv64",
        ],
        root_module_direct_dev_deps = [],
    )

_archive = tag_class(attrs = {})

busybox = module_extension(
    implementation = _busybox_impl,
    tag_classes = {
        "archive": _archive,
    },
)


================================================
FILE: private/extensions/node.bzl
================================================
"node"

BUILD_TMPL = """\
# GENERATED BY node_archive.bzl
load("@distroless//private/pkg:debian_spdx.bzl", "debian_spdx")
load("@distroless//private/util:merge_providers.bzl", "merge_providers")
load("@distroless//private/util:tar.bzl", "tar")

tar(
    name = "data",
    extension = "tar.gz",
    srcs = glob(
        [
            "output/bin/node",
            "output/LICENSE",
        ],
    ),
    package_dir = "/nodejs",
    strip_prefix = "external/{canonical_name}/output"
)

tar(
    name = "_control",
    extension = "tar.gz",
    srcs = ["control"]
)

debian_spdx(
    name = "spdx",
    control = ":_control",
    data = ":data",
    package_name = "{package_name}",
    spdx_id = "{spdx_id}",
    sha256 = "{sha256}",
    urls = [{urls}]
)

merge_providers(
    name = "{name}",
    srcs = [":data", ":spdx"],
    visibility = ["//visibility:public"],
)
"""

def _impl(rctx):
    rctx.report_progress("Fetching {}".format(rctx.attr.package_name))
    rctx.download_and_extract(
        url = rctx.attr.urls,
        sha256 = rctx.attr.sha256,
        type = rctx.attr.type,
        stripPrefix = rctx.attr.strip_prefix,
        output = "output",
    )
    rctx.template(
        "control",
        rctx.attr.control,
        substitutions = {
            "{{VERSION}}": rctx.attr.version,
            "{{ARCHITECTURE}}": rctx.attr.architecture,
            "{{SHA256}}": rctx.attr.sha256,
        },
    )
    rctx.file(
        "BUILD.bazel",
        content = BUILD_TMPL.format(
            canonical_name = rctx.attr.name,
            name = rctx.attr.name.split("~")[-1],
            package_name = rctx.attr.package_name,
            spdx_id = rctx.attr.name,
            urls = ",".join(['"%s"' % url for url in rctx.attr.urls]),
            sha256 = rctx.attr.sha256,
        ),
    )

node_archive = repository_rule(
    implementation = _impl,
    attrs = {
        "urls": attr.string_list(mandatory = True),
        "sha256": attr.string(mandatory = True),
        "type": attr.string(default = ".tar.gz"),
        "strip_prefix": attr.string(),
        "package_name": attr.string(default = "nodejs"),
        "version": attr.string(mandatory = True),
        "architecture": attr.string(mandatory = True),
        # control is only used to populate the sbom, see https://github.com/GoogleContainerTools/distroless/issues/1373
        # for why writing debian control files to the image is incompatible with scanners.
        "control": attr.label(),
    },
)

def _node_impl(module_ctx):
    mod = module_ctx.modules[0]

    if len(module_ctx.modules) > 1:
        fail("node.archive should be called only once")
    if not mod.is_root:
        fail("node.archive should be called from root module only.")

    # Node (https://nodejs.org/en/about/releases/)
    # Follow Node's maintainence schedule and support all LTS versions that are not end of life

    node_archive(
        name = "nodejs20_amd64",
        sha256 = "6362e50804cdcc110592201f67beda93bcd702fdcbe1c42840a50d590e3af0ce",
        strip_prefix = "node-v20.20.1-linux-x64/",
        urls = ["https://nodejs.org/dist/v20.20.1/node-v20.20.1-linux-x64.tar.gz"],
        version = "20.20.1",
        architecture = "amd64",
        control = "//nodejs:control",
    )

    node_archive(
        name = "nodejs20_arm64",
        sha256 = "d6947e10ddc124284aee92981cc739cd4581a6b4bf89520792f2b582600195fa",
        strip_prefix = "node-v20.20.1-linux-arm64/",
        urls = ["https://nodejs.org/dist/v20.20.1/node-v20.20.1-linux-arm64.tar.gz"],
        version = "20.20.1",
        architecture = "arm64",
        control = "//nodejs:control",
    )

    node_archive(
        name = "nodejs20_arm",
        sha256 = "6d7b0f1f3a88004b251fd4d2ee2f5c4faca15587a3cdaaefae11e655b9e01cde",
        strip_prefix = "node-v20.20.1-linux-armv7l/",
        urls = ["https://nodejs.org/dist/v20.20.1/node-v20.20.1-linux-armv7l.tar.gz"],
        version = "20.20.1",
        architecture = "arm",
        control = "//nodejs:control",
    )

    node_archive(
        name = "nodejs20_ppc64le",
        sha256 = "d885a22c1a08bd2d2f91c290770f3a9762075c4ec222fcd9d9fc921b0c8294b9",
        strip_prefix = "node-v20.20.1-linux-ppc64le/",
        urls = ["https://nodejs.org/dist/v20.20.1/node-v20.20.1-linux-ppc64le.tar.gz"],
        version = "20.20.1",
        architecture = "ppc64le",
        control = "//nodejs:control",
    )

    node_archive(
        name = "nodejs20_s390x",
        sha256 = "7f0688d98fce89a9d7c623a3b0d2eab8374333f31461c8cb8b9d34712c45a1ba",
        strip_prefix = "node-v20.20.1-linux-s390x/",
        urls = ["https://nodejs.org/dist/v20.20.1/node-v20.20.1-linux-s390x.tar.gz"],
        version = "20.20.1",
        architecture = "s390x",
        control = "//nodejs:control",
    )

    node_archive(
        name = "nodejs22_amd64",
        sha256 = "07c8aafa60644fb81adefa1ee7da860eb1920851ffdc9a37020ab0be47fbc10e",
        strip_prefix = "node-v22.22.1-linux-x64/",
        urls = ["https://nodejs.org/dist/v22.22.1/node-v22.22.1-linux-x64.tar.gz"],
        version = "22.22.1",
        architecture = "amd64",
        control = "//nodejs:control",
    )

    node_archive(
        name = "nodejs22_arm64",
        sha256 = "1d1690e9aba47e887a275abc6d8f7317e571a0700deaef493f768377e99155f5",
        strip_prefix = "node-v22.22.1-linux-arm64/",
        urls = ["https://nodejs.org/dist/v22.22.1/node-v22.22.1-linux-arm64.tar.gz"],
        version = "22.22.1",
        architecture = "arm64",
        control = "//nodejs:control",
    )

    node_archive(
        name = "nodejs22_arm",
        sha256 = "2b592d21609ef299d1e3918bb806ed62ba715d4109b0f8ec11b132af9fa42d70",
        strip_prefix = "node-v22.22.1-linux-armv7l/",
        urls = ["https://nodejs.org/dist/v22.22.1/node-v22.22.1-linux-armv7l.tar.gz"],
        version = "22.22.1",
        architecture = "arm",
        control = "//nodejs:control",
    )

    node_archive(
        name = "nodejs22_ppc64le",
        sha256 = "18f9ab7da4f3a04ec213590b14e5d78b60bfb5c6b8bf53541e7eaf1adf9d270a",
        strip_prefix = "node-v22.22.1-linux-ppc64le/",
        urls = ["https://nodejs.org/dist/v22.22.1/node-v22.22.1-linux-ppc64le.tar.gz"],
        version = "22.22.1",
        architecture = "ppc64le",
        control = "//nodejs:control",
    )

    node_archive(
        name = "nodejs22_s390x",
        sha256 = "6128f9d54a1b43258144d7ac074a82d3b6c96d8e9cb3a8e14e4722a66990adbe",
        strip_prefix = "node-v22.22.1-linux-s390x/",
        urls = ["https://nodejs.org/dist/v22.22.1/node-v22.22.1-linux-s390x.tar.gz"],
        version = "22.22.1",
        architecture = "s390x",
        control = "//nodejs:control",
    )

    node_archive(
        name = "nodejs24_amd64",
        sha256 = "dbf5b8665dec15e59e6359a517fefb47b23fdb9152d8def975b9bca3dfc6d355",
        strip_prefix = "node-v24.14.0-linux-x64/",
        urls = ["https://nodejs.org/dist/v24.14.0/node-v24.14.0-linux-x64.tar.gz"],
        version = "24.14.0",
        architecture = "amd64",
        control = "//nodejs:control",
    )

    node_archive(
        name = "nodejs24_arm64",
        sha256 = "f44740cd218de8127f1c44c41510a3a740fa5c9c8d1cdce1c3bedada79f3cde7",
        strip_prefix = "node-v24.14.0-linux-arm64/",
        urls = ["https://nodejs.org/dist/v24.14.0/node-v24.14.0-linux-arm64.tar.gz"],
        version = "24.14.0",
        architecture = "arm64",
        control = "//nodejs:control",
    )

    node_archive(
        name = "nodejs24_ppc64le",
        sha256 = "83b263f9c2ea946c0c4a15c3caea6470dc49fe0beb6f33dfd29aa9128250637a",
        strip_prefix = "node-v24.14.0-linux-ppc64le/",
        urls = ["https://nodejs.org/dist/v24.14.0/node-v24.14.0-linux-ppc64le.tar.gz"],
        version = "24.14.0",
        architecture = "ppc64le",
        control = "//nodejs:control",
    )

    node_archive(
        name = "nodejs24_s390x",
        sha256 = "8fa220a1f7b7769605c2e929fdbf736822997bf4cf88a3db05188eabd7712328",
        strip_prefix = "node-v24.14.0-linux-s390x/",
        urls = ["https://nodejs.org/dist/v24.14.0/node-v24.14.0-linux-s390x.tar.gz"],
        version = "24.14.0",
        architecture = "s390x",
        control = "//nodejs:control",
    )

    return module_ctx.extension_metadata(
        root_module_direct_deps = [
            "nodejs20_amd64",
            "nodejs20_arm64",
            "nodejs20_arm",
            "nodejs20_ppc64le",
            "nodejs20_s390x",
            "nodejs22_amd64",
            "nodejs22_arm64",
            "nodejs22_arm",
            "nodejs22_ppc64le",
            "nodejs22_s390x",
            "nodejs24_amd64",
            "nodejs24_arm64",
            "nodejs24_ppc64le",
            "nodejs24_s390x",
        ],
        root_module_direct_dev_deps = [],
    )

_archive = tag_class(attrs = {})

node = module_extension(
    implementation = _node_impl,
    tag_classes = {
        "archive": _archive,
    },
)


================================================
FILE: private/extensions/version.bzl
================================================
"generates version information from lockfiles"

# buildifier: disable=bzl-visibility
load("@rules_distroless//apt/private:version.bzl", _version = "version")

_VERSIONS_TMPL = """\
"versions repo"

# AUTO GENERATED. DO NOT EDIT.
_versions = {}

# buildifier: disable=function-docstring
def version(dist, arch, name):
    if dist not in _versions:
        fail("unknown dist {{}}".format(dist))
    if name not in _versions[dist]:
        fail("unknown package {{}}".format(name))
    if arch not in _versions[dist][name]:
        fail("unknown arch {{}}".format(arch))
    return struct(**_versions[dist][name][arch])
"""

def _parse_version(raw):
    (epoch, upstream, revision) = _version.parse(raw)

    return struct(raw = raw, epoch = epoch, upstream = upstream, revision = revision)

def _version_repo_impl(rctx):
    rctx.file("versions.bzl", _VERSIONS_TMPL.format(json.decode(rctx.attr.versions)))
    rctx.file("BUILD.bazel", "exports_files(['versions.bzl'])")

version_repo = repository_rule(
    implementation = _version_repo_impl,
    attrs = {
        "versions": attr.string(),
    },
)

def _version_impl(module_ctx):
    versions = dict()

    for mod in module_ctx.modules:
        for from_lock in mod.tags.from_lock:
            lock = json.decode(module_ctx.read(from_lock.lock))
            repo = from_lock.repo_name

            if lock["version"] != 1:
                fail("unknown lock version")

            if repo not in versions:
                versions[repo] = dict()

            for pkg in lock["packages"]:
                if pkg["name"] not in versions[repo]:
                    versions[repo][pkg["name"]] = dict()

                versions[repo][pkg["name"]][pkg["arch"]] = _parse_version(pkg["version"])

    version_repo(
        name = "versions",
        versions = json.encode(versions),
    )

    return module_ctx.extension_metadata(
        root_module_direct_deps = ["versions"],
        root_module_direct_dev_deps = [],
    )

_from_lock = tag_class(attrs = {
    "repo_name": attr.string(mandatory = True),
    "lock": attr.label(mandatory = True),
})

version = module_extension(
    implementation = _version_impl,
    tag_classes = {
        "from_lock": _from_lock,
    },
)


================================================
FILE: private/oci/BUILD.bazel
================================================
exports_files(["sign_and_push.sh.tpl"])


================================================
FILE: private/oci/cc_image.bzl
================================================
"cc_image rule for creating C++ container images"

load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
load("@rules_oci//oci:defs.bzl", "oci_image")
load("//private/util:tar.bzl", "tar")

def cc_image(name, srcs, base):
    cc_binary(
        name = "%s_binary" % name,
        srcs = srcs,
    )

    tar(
        name = "%s_layer" % name,
        extension = "tar.gz",
        srcs = [
            ":%s_binary" % name,
        ],
    )

    oci_image(
        name = name,
        base = base,
        entrypoint = [
            "/%s_binary" % name,
        ],
        tars = [
            ":%s_layer" % name,
        ],
    )


================================================
FILE: private/oci/defs.bzl
================================================
load(":cc_image.bzl", _cc_image = "cc_image")
load(":go_image.bzl", _go_image = "go_image")
load(":java_image.bzl", _java_image = "java_image")
load(":rust_image.bzl", _rust_image = "rust_image")
load(":sign_and_push.bzl", _sign_and_push_all = "sign_and_push_all")

java_image = _java_image
cc_image = _cc_image
rust_image = _rust_image
go_image = _go_image
sign_and_push_all = _sign_and_push_all


================================================
FILE: private/oci/digest.bzl
================================================
"generate digest for oci_image and oci_image_index"

load("@aspect_bazel_lib//lib:copy_file.bzl", "copy_file")
load("@aspect_bazel_lib//lib:directory_path.bzl", "directory_path")
load("@aspect_bazel_lib//lib:jq.bzl", "jq")

# Normally we'd use the `.digest` target that rules_oci creates for every oci_image but
# we also use oci_image_index which does not have a digest target. This was fixed in
# https://github.com/bazel-contrib/rules_oci/pull/742 but it on the 2.x releases of rules_oci
# TODO: Remove this once we upgrade to rules_oci 2.x
def digest(name, image, **kwargs):
    # `oci_image_rule` and `oci_image_index_rule` produce a directory as default output.
    # Label for the [name]/index.json file
    directory_path(
        name = "_{}_index_json".format(name),
        directory = image,
        path = "index.json",
        **kwargs
    )

    copy_file(
        name = "_{}_index_json_cp".format(name),
        src = "_{}_index_json".format(name),
        out = "_{}_index.json".format(name),
        **kwargs
    )

    # Matches the [name].digest target produced by rules_docker container_image
    jq(
        name = name,
        args = ["--raw-output"],
        srcs = ["_{}_index.json".format(name)],
        filter = """.manifests[0].digest""",
        out = name + ".json.sha256",  # path chosen to match rules_docker for easy migration
        **kwargs
    )


================================================
FILE: private/oci/go_image.bzl
================================================
"go_image rule for creating Go container images"

load("@rules_go//go:def.bzl", "go_binary")
load("@rules_oci//oci:defs.bzl", "oci_image")
load("//private/util:tar.bzl", "tar")

def go_image(name, srcs, base, arch = "amd64", os = "linux"):
    go_binary(
        name = "{}_binary".format(name),
        srcs = srcs,
        goarch = arch,
        goos = os,
        pure = "on",
    )

    tar(
        name = "{}_layer".format(name),
        extension = "tar.gz",
        srcs = ["{}_binary".format(name)],
    )

    oci_image(
        name = name,
        base = base,
        entrypoint = ["/{}_binary".format(name)],
        tars = [
            "{}_layer".format(name),
        ],
    )


================================================
FILE: private/oci/java_image.bzl
================================================
"java_image rule for creating Java container images"

load("@rules_oci//oci:defs.bzl", "oci_image")
load("//private/util:tar.bzl", "tar")

def java_image(name, srcs, main_class, base):
    native.java_binary(
        name = "%s_binary" % name,
        srcs = srcs,
        main_class = main_class,
    )

    tar(
        name = "%s_layer" % name,
        extension = "tar.gz",
        srcs = [
            ":%s_binary" % name,
        ],
    )

    oci_image(
        name = name,
        base = base,
        cmd = [
            "/%s_binary.jar" % name,
        ],
        tars = [
            ":%s_layer" % name,
        ],
    )


================================================
FILE: private/oci/rust_image.bzl
================================================
"rust_image rule for creating Rust container images"

load("@rules_oci//oci:defs.bzl", "oci_image")
load("@rules_rust//rust:defs.bzl", "rust_binary")
load("//private/util:tar.bzl", "tar")

def rust_image(name, srcs, base, tags):
    rust_binary(
        name = "%s_binary" % name,
        srcs = srcs,
        tags = tags,
    )

    tar(
        name = "%s_layer" % name,
        extension = "tar.gz",
        srcs = [
            ":%s_binary" % name,
        ],
        tags = tags,
    )

    oci_image(
        name = name,
        base = base,
        entrypoint = [
            "/%s_binary" % name,
        ],
        tars = [
            ":%s_layer" % name,
        ],
        tags = tags,
    )


================================================
FILE: private/oci/sign_and_push.bzl
================================================
"rules for signing, attesting and pushing images"

load("@bazel_skylib//rules:write_file.bzl", "write_file")
load("//private/pkg:oci_image_spdx.bzl", "oci_image_spdx")
load(":digest.bzl", "digest")

PUSH_AND_SIGN_CMD = """\
# Push {IMAGE}
repository="$(stamp "{REPOSITORY}")"
tag="$(stamp "{TAG}")"
digest="$(cat {DIGEST})"
echo "Pushing $repository@$digest"
{CRANE} push {IMAGE} "$repository@$digest"

# Check for signature and skip signing/sbom if present
if ! {COSIGN} verify "$repository@$digest" --certificate-oidc-issuer https://accounts.google.com --certificate-identity "${{KEYLESS}}" > /dev/null; then
    {COSIGN} attest "$repository@$digest" --predicate "{SBOM}" --type "spdx" --yes
    {COSIGN} sign "$repository@$digest" --yes
fi

{CRANE} tag "$repository@$digest" "$tag"
"""

TAG_CMD = """\
# Tag {IMAGE}
from="$(stamp "{FROM}")"
tag="$(stamp "{TAG}")"
{CRANE} tag "$from" "$tag"
"""

def _sign_and_push_impl(ctx):
    cmds = []

    runfiles = ctx.runfiles(files = ctx.files.targets + [ctx.version_file, ctx.file._crane, ctx.file._cosign])

    for (image, target) in ctx.attr.targets.items():
        files = target[DefaultInfo].files.to_list()

        all_refs = ctx.attr.refs[image]
        for ref in all_refs:
            repository_and_tag = ref.split(":")
            cmds.append(
                PUSH_AND_SIGN_CMD.format(
                    IMAGE = files[0].short_path,
                    SBOM = files[1].short_path,
                    DIGEST = files[2].short_path,
                    CRANE = ctx.file._crane.short_path,
                    COSIGN = ctx.file._cosign.short_path,
                    REPOSITORY = repository_and_tag[0],
                    T
Download .txt
gitextract_vcjsnv1p/

├── .bazelignore
├── .bazelrc
├── .bazelversion
├── .cloudbuild/
│   ├── cloudbuild.yaml
│   ├── lifecycle_tag.sh
│   ├── lifecycle_tag.yaml
│   └── release.sh
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── actionable-debian-cve.md
│   │   ├── actionable-non-debian-cve.md
│   │   └── bug_report.md
│   ├── dependabot.yml
│   └── workflows/
│       ├── buildifier.yaml
│       ├── check-ldconfig.yaml
│       ├── ci.bazelrc
│       ├── ci.yaml
│       ├── config-diff.yaml
│       ├── examples.yaml
│       ├── image-check.yaml
│       ├── scorecards-analysis.yml
│       ├── update-deb-package-non-snapshots.yml
│       ├── update-deb-package-snapshots.yml
│       └── update-node-archives.yml
├── .gitignore
├── .pre-commit-config.yaml
├── .prettierignore
├── BUILD
├── CONTRIBUTING.md
├── LICENSE
├── MODULE.bazel
├── PACKAGE_METADATA.md
├── README.md
├── RELEASES.md
├── SECURITY.md
├── SUPPORT_POLICY.md
├── WORKSPACE
├── base/
│   ├── BUILD
│   ├── README.md
│   ├── base.bzl
│   ├── config.bzl
│   ├── test.sh
│   └── testdata/
│       ├── base.yaml
│       └── debug.yaml
├── cc/
│   ├── BUILD
│   ├── README.md
│   ├── cc.bzl
│   └── config.bzl
├── common/
│   ├── BUILD.bazel
│   └── variables.bzl
├── cosign.pub
├── distro.bzl
├── downloader.cfg
├── examples/
│   ├── BUILD
│   ├── cc/
│   │   ├── BUILD
│   │   ├── Dockerfile
│   │   ├── hello.c
│   │   ├── hello_cc.cc
│   │   └── testdata/
│   │       ├── hello_cc_debian12.yaml
│   │       ├── hello_cc_debian13.yaml
│   │       ├── hello_debian12.yaml
│   │       └── hello_debian13.yaml
│   ├── go/
│   │   ├── BUILD
│   │   ├── Dockerfile
│   │   ├── go.mod
│   │   ├── main.go
│   │   └── main_test.go
│   ├── java/
│   │   ├── BUILD
│   │   ├── Dockerfile
│   │   ├── HelloJava.java
│   │   └── testdata/
│   │       ├── hello_nonroot_debian13.yaml
│   │       └── hello_root_debian13.yaml
│   ├── nodejs/
│   │   ├── BUILD
│   │   ├── Dockerfile
│   │   ├── hello.js
│   │   ├── hello_http.js
│   │   ├── node-express/
│   │   │   ├── Dockerfile
│   │   │   ├── hello_express.js
│   │   │   └── package.json
│   │   ├── package.json
│   │   └── testdata/
│   │       └── hello.yaml
│   ├── nonroot/
│   │   ├── BUILD
│   │   └── testdata/
│   │       ├── user.go
│   │       └── user.yaml
│   ├── python3/
│   │   ├── BUILD
│   │   ├── Dockerfile
│   │   └── hello.py
│   ├── python3-requirements/
│   │   ├── Dockerfile
│   │   ├── README.md
│   │   ├── psutil_example.py
│   │   └── requirements.txt
│   ├── rust/
│   │   ├── .dockerignore
│   │   ├── BUILD
│   │   ├── Cargo.toml
│   │   ├── Dockerfile
│   │   └── src/
│   │       └── main.rs
│   └── test.sh
├── experimental/
│   ├── BUILD
│   └── busybox/
│       ├── BUILD
│       └── commands.bzl
├── java/
│   ├── BUILD
│   ├── README.md
│   ├── config.bzl
│   ├── control
│   ├── java.bzl
│   ├── jre_ver.bzl
│   └── testdata/
│       ├── CheckCerts.java
│       ├── CheckEncoding.java
│       ├── CheckLibharfbuzz.java
│       ├── java17_debian13.yaml
│       ├── java17_debug_debian13.yaml
│       ├── java21_debian13.yaml
│       ├── java21_debug_debian13.yaml
│       ├── java25_debian13.yaml
│       ├── java25_debug_debian13.yaml
│       ├── java_base.yaml
│       ├── java_base_debug.yaml
│       ├── java_certs.yaml
│       ├── java_encoding.yaml
│       └── java_libharfbuzz.yaml
├── knife
├── knife.d/
│   ├── update_java_versions.sh
│   └── update_node_archives.js
├── nodejs/
│   ├── BUILD
│   ├── README.md
│   ├── config.bzl
│   ├── control
│   ├── nodejs.bzl
│   └── testdata/
│       ├── check_certificate.js
│       ├── check_certificate.yaml
│       ├── check_headers.yaml
│       ├── check_npm.yaml
│       ├── nodejs20.yaml
│       ├── nodejs22.yaml
│       └── nodejs24.yaml
├── private/
│   ├── extensions/
│   │   ├── BUILD.bazel
│   │   ├── busybox.bzl
│   │   ├── node.bzl
│   │   └── version.bzl
│   ├── oci/
│   │   ├── BUILD.bazel
│   │   ├── cc_image.bzl
│   │   ├── defs.bzl
│   │   ├── digest.bzl
│   │   ├── go_image.bzl
│   │   ├── java_image.bzl
│   │   ├── rust_image.bzl
│   │   ├── sign_and_push.bzl
│   │   └── sign_and_push.sh.tpl
│   ├── pkg/
│   │   ├── BUILD.bazel
│   │   ├── debian_spdx.bzl
│   │   ├── debian_spdx.go
│   │   ├── oci_image_spdx.bzl
│   │   ├── oci_image_spdx.go
│   │   └── test/
│   │       └── oci_image/
│   │           ├── BUILD.bazel
│   │           ├── fat_image_sbom.spdx.json
│   │           ├── image_amd64.spdx.json
│   │           └── image_arm64.spdx.json
│   ├── repos/
│   │   └── deb/
│   │       ├── BUILD.bazel
│   │       ├── bookworm.lock.json
│   │       ├── bookworm.yaml
│   │       ├── bookworm_python.lock.json
│   │       ├── bookworm_python.yaml
│   │       ├── deb.MODULE.bazel
│   │       ├── package.BUILD.tmpl
│   │       ├── trixie.lock.json
│   │       ├── trixie.yaml
│   │       ├── trixie_adoptium.lock.json
│   │       ├── trixie_adoptium.yaml
│   │       ├── trixie_java.lock.json
│   │       ├── trixie_java.yaml
│   │       ├── trixie_python.lock.json
│   │       └── trixie_python.yaml
│   ├── stamp.bash
│   ├── tools/
│   │   ├── diff/
│   │   │   ├── BUILD.bazel
│   │   │   └── diff.bash
│   │   └── lifecycle/
│   │       ├── BUILD.bazel
│   │       ├── defs.bzl
│   │       ├── tag.bzl
│   │       ├── tag.sh
│   │       └── tag.sh.README.md
│   └── util/
│       ├── BUILD
│       ├── deb.bzl
│       ├── extract.bzl
│       ├── java_cacerts.bzl
│       ├── merge_providers.bzl
│       └── tar.bzl
├── python3/
│   ├── BUILD
│   ├── README.md
│   ├── config.bzl
│   ├── ldconfig/
│   │   ├── ld.so.cache.amd64
│   │   ├── ld.so.cache.arm64
│   │   └── ldconfig.sh
│   ├── ldconfig.bzl
│   ├── python.bzl
│   └── testdata/
│       ├── debian12.yaml
│       ├── debian13.yaml
│       └── python3.yaml
└── static/
    ├── BUILD
    ├── config.bzl
    ├── nsswitch.conf
    ├── static.bzl
    └── testdata/
        ├── check_certs.go
        ├── debian12.yaml
        ├── debian13.yaml
        ├── debug.yaml
        └── static.yaml
Download .txt
SYMBOL INDEX (26 symbols across 15 files)

FILE: examples/cc/hello.c
  function main (line 3) | int main() {

FILE: examples/cc/hello_cc.cc
  function main (line 3) | int main() {

FILE: examples/go/main.go
  function main (line 19) | func main() {

FILE: examples/go/main_test.go
  function Test1 (line 7) | func Test1(t *testing.T) {

FILE: examples/java/HelloJava.java
  class HelloJava (line 3) | public class HelloJava {
    method main (line 4) | public static void main(String[] args) {

FILE: examples/nonroot/testdata/user.go
  function main (line 25) | func main() {

FILE: examples/python3-requirements/psutil_example.py
  function mib (line 8) | def mib(total_bytes):
  function main (line 13) | def main():

FILE: examples/python3/hello.py
  function main (line 24) | def main(args):

FILE: examples/rust/src/main.rs
  function main (line 1) | fn main() {

FILE: java/testdata/CheckCerts.java
  class CheckCerts (line 7) | public class CheckCerts {
    method main (line 9) | public static void main(String[] args) throws IOException {

FILE: java/testdata/CheckEncoding.java
  class CheckEncoding (line 6) | public class CheckEncoding {
    method main (line 8) | public static void main(String[] args) {

FILE: java/testdata/CheckLibharfbuzz.java
  class CheckLibharfbuzz (line 6) | public class CheckLibharfbuzz {
    method main (line 7) | public static void main(String[] args) {

FILE: private/pkg/debian_spdx.go
  function pkgId (line 18) | func pkgId(id string) string {
  function parseDebControl (line 28) | func parseDebControl(r io.Reader) (map[string]string, error) {
  function main (line 72) | func main() {

FILE: private/pkg/oci_image_spdx.go
  function pkgId (line 15) | func pkgId(id string) string {
  type ArrayFlags (line 24) | type ArrayFlags
    method String (line 26) | func (i *ArrayFlags) String() string {
    method Set (line 30) | func (i *ArrayFlags) Set(value string) error {
  function main (line 35) | func main() {

FILE: static/testdata/check_certs.go
  function main (line 23) | func main() {
Condensed preview — 204 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (467K chars).
[
  {
    "path": ".bazelignore",
    "chars": 0,
    "preview": ""
  },
  {
    "path": ".bazelrc",
    "chars": 1664,
    "preview": "# Bazel settings that apply to this repository.\n# Take care to document any settings that you expect users to apply.\n# S"
  },
  {
    "path": ".bazelversion",
    "chars": 5,
    "preview": "7.4.0"
  },
  {
    "path": ".cloudbuild/cloudbuild.yaml",
    "chars": 583,
    "preview": "timeout: 10800s  # 3 hours\n\noptions:\n  machineType: E2_HIGHCPU_32\n  logging: CLOUD_LOGGING_ONLY\n\nartifacts:\n  objects:\n "
  },
  {
    "path": ".cloudbuild/lifecycle_tag.sh",
    "chars": 1074,
    "preview": "#!/usr/bin/env bash\nset -o errexit -o nounset -o xtrace -o pipefail\n\nBAZELISK_VERSION=\"1.27.0\"\nBAZELISK_SHA256=\"e1508323"
  },
  {
    "path": ".cloudbuild/lifecycle_tag.yaml",
    "chars": 349,
    "preview": "timeout: 1800s # 30 minutes\n\noptions:\n  machineType: E2_MEDIUM\n  logging: CLOUD_LOGGING_ONLY\n\nsteps:\n  - name: gcr.io/cl"
  },
  {
    "path": ".cloudbuild/release.sh",
    "chars": 1565,
    "preview": "#!/usr/bin/env bash\nset -o errexit -o xtrace -o pipefail\n\nBAZEL_REMOTE_VERSION=\"2.6.1\"\nBAZEL_REMOTE_SHA256=\"025d53aeb03a"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/actionable-debian-cve.md",
    "chars": 652,
    "preview": "---\nname: Actionable debian CVE\nabout: A CVE from a debian package where a fix is available\ntitle: ''\nlabels: ''\nassigne"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/actionable-non-debian-cve.md",
    "chars": 336,
    "preview": "---\nname: Actionable non debian CVE\nabout: A CVE is an package imported into distroless that is not from debian\ntitle: '"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 472,
    "preview": "---\nname: Bug report\nabout: There is an issue with how distroless is built\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**De"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 291,
    "preview": "version: 2\nupdates:\n- package-ecosystem: \"gomod\"\n  directory: \"/\"\n  schedule:\n    interval: \"daily\"\n- package-ecosystem:"
  },
  {
    "path": ".github/workflows/buildifier.yaml",
    "chars": 547,
    "preview": "name: Buildifier\n\non:\n  pull_request:\n    branches: [\"main\"]\n\npermissions:\n  contents: read\n\njobs:\n  autoformat:\n    nam"
  },
  {
    "path": ".github/workflows/check-ldconfig.yaml",
    "chars": 467,
    "preview": "name: Check ldconfig cache\n\non:\n  pull_request:\n    branches: [\"main\"]\n  push:\n    branches: [\"main\"]\n\npermissions:\n  co"
  },
  {
    "path": ".github/workflows/ci.bazelrc",
    "chars": 201,
    "preview": "build --announce_rc\nbuild --repository_cache=~/.cache/bazel-repo\n# intentionally disable build cache, it gets too big\n# "
  },
  {
    "path": ".github/workflows/ci.yaml",
    "chars": 2327,
    "preview": "name: CI\n\non:\n  workflow_dispatch: # Allow manual runs.\n  pull_request:\n    branches: [\"main\"]\n  push:\n    branches: [\"m"
  },
  {
    "path": ".github/workflows/config-diff.yaml",
    "chars": 2757,
    "preview": "name: Config Check\n\non:\n  workflow_dispatch:\n  pull_request:\n    branches: [\"main\"]\n\nconcurrency:\n  group: ${{ github.wo"
  },
  {
    "path": ".github/workflows/examples.yaml",
    "chars": 249,
    "preview": "name: Examples\n\non:\n  pull_request:\n    branches: [ 'main' ]\n\npermissions:\n  contents: read\n\njobs:\n  examples:\n    name:"
  },
  {
    "path": ".github/workflows/image-check.yaml",
    "chars": 3983,
    "preview": "name: Image Check\n\non:\n  workflow_dispatch:\n  pull_request:\n    branches: [\"main\"]\n\nconcurrency:\n  group: ${{ github.wor"
  },
  {
    "path": ".github/workflows/scorecards-analysis.yml",
    "chars": 1593,
    "preview": "name: Scorecards supply-chain security\non: \n  # Only the default branch is supported.\n  branch_protection_rule:\n  schedu"
  },
  {
    "path": ".github/workflows/update-deb-package-non-snapshots.yml",
    "chars": 2013,
    "preview": "name: update-non-snapshots\non:\n  # will send emails to last editor of this cron syntax (distroless-bot)\n  schedule:\n    "
  },
  {
    "path": ".github/workflows/update-deb-package-snapshots.yml",
    "chars": 2362,
    "preview": "name: update-snapshots\non:\n  # will send emails to last editor of this cron syntax (distroless-bot)\n  schedule:\n    - cr"
  },
  {
    "path": ".github/workflows/update-node-archives.yml",
    "chars": 1844,
    "preview": "name: update-node-packages\non:\n  # will send emails to last editor of this cron syntax (distroless-bot)\n  schedule:\n    "
  },
  {
    "path": ".gitignore",
    "chars": 411,
    "preview": "# Ignore backup files.\n*~\n# Ignore Vim swap files.\n.*.swp\n# Ignore files generated by IDEs.\n/.classpath\n/.factorypath\n.i"
  },
  {
    "path": ".pre-commit-config.yaml",
    "chars": 974,
    "preview": "# See CONTRIBUTING.md for instructions.\n# See https://pre-commit.com for more information\n# See https://pre-commit.com/h"
  },
  {
    "path": ".prettierignore",
    "chars": 28,
    "preview": "private/pkg/test/oci_image/*"
  },
  {
    "path": "BUILD",
    "chars": 10257,
    "preview": "load(\"//base:config.bzl\", \"BASE_ARCHITECTURES\", \"BASE_DISTROS\")\nload(\"//cc:config.bzl\", \"CC_ARCHITECTURES\", \"CC_DISTROS\""
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 2744,
    "preview": "# How to become a contributor and submit your own code\n\n## Contributor License Agreements\n\nWe'd love to accept your patc"
  },
  {
    "path": "LICENSE",
    "chars": 11358,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "MODULE.bazel",
    "chars": 2045,
    "preview": "\"distroless\"\n\nmodule(name = \"distroless\")\n\nbazel_dep(name = \"bazel_skylib\", version = \"1.8.2\")\nbazel_dep(name = \"aspect_"
  },
  {
    "path": "PACKAGE_METADATA.md",
    "chars": 2568,
    "preview": "# Package Metadata\n\n## `dpkg` Metadata Structure Standard\n\n### Overview\n\nThis document defines the standard structure fo"
  },
  {
    "path": "README.md",
    "chars": 11379,
    "preview": "# \"Distroless\" Container Images.\n\n[![CI Build Status](https://github.com/GoogleContainerTools/distroless/actions/workflo"
  },
  {
    "path": "RELEASES.md",
    "chars": 328,
    "preview": "# Release Process\n\nImages in this repository are built and released in the `gcr.io/distroless` repository on the [Google"
  },
  {
    "path": "SECURITY.md",
    "chars": 1335,
    "preview": "# Security Policy\n\n## Supported Versions\n\nDistroless currently tracks debian 12 ([bookworm](https://packages.debian.org/"
  },
  {
    "path": "SUPPORT_POLICY.md",
    "chars": 1421,
    "preview": "Distroless currently tracks Debian on their [standard support timeline](https://wiki.debian.org/DebianReleases#Productio"
  },
  {
    "path": "WORKSPACE",
    "chars": 123,
    "preview": "load(\"@rules_oci//cosign:repositories.bzl\", \"cosign_register_toolchains\")\n\ncosign_register_toolchains(name = \"oci_cosign"
  },
  {
    "path": "base/BUILD",
    "chars": 964,
    "preview": "load(\":base.bzl\", \"base_image\", \"base_image_index\", \"base_nossl_image\", \"base_nossl_image_index\")\nload(\":config.bzl\", \"B"
  },
  {
    "path": "base/README.md",
    "chars": 1293,
    "preview": "# Documentation for `gcr.io/distroless/base`, `gcr.io/distroless/base-nossl` and `gcr.io/distroless/static`\n\n## Image Co"
  },
  {
    "path": "base/base.bzl",
    "chars": 3674,
    "preview": "\"defines a function to replicate the container images for different distributions\"\n\nload(\"@container_structure_test//:de"
  },
  {
    "path": "base/config.bzl",
    "chars": 497,
    "preview": "BASE_DISTROS = [\"debian12\", \"debian13\"]\nBASE_ARCHITECTURES = {\n    \"debian12\": [\"amd64\", \"arm64\", \"arm\", \"s390x\", \"ppc64"
  },
  {
    "path": "base/test.sh",
    "chars": 180,
    "preview": "$RUNFILES_DIR/runtimes_common/structure_tests/ext_run.sh \\\n  -i bazel/base:cc \\\n  -t $RUNFILES_DIR/distroless/base/base."
  },
  {
    "path": "base/testdata/base.yaml",
    "chars": 1631,
    "preview": "schemaVersion: \"1.0.0\"\nfileExistenceTests:\n# Basic FS sanity checks.\n- name: root\n  path: '/'\n  shouldExist: true\n- name"
  },
  {
    "path": "base/testdata/debug.yaml",
    "chars": 232,
    "preview": "schemaVersion: \"1.0.0\"\nfileExistenceTests:\n# Basic FS sanity checks.\n- name: busybox\n  path: '/busybox'\n  shouldExist: t"
  },
  {
    "path": "cc/BUILD",
    "chars": 486,
    "preview": "load(\":cc.bzl\", \"cc_image\", \"cc_image_index\")\nload(\":config.bzl\", \"CC_ARCHITECTURES\", \"CC_DISTROS\", \"CC_PACKAGES\")\n\npack"
  },
  {
    "path": "cc/README.md",
    "chars": 406,
    "preview": "# Documentation for `gcr.io/distroless/cc`\n\n## Image Contents\n\nThis image contains a minimal Linux, glibc runtime for \"m"
  },
  {
    "path": "cc/cc.bzl",
    "chars": 1236,
    "preview": "load(\"@rules_oci//oci:defs.bzl\", \"oci_image\", \"oci_image_index\")\nload(\"//common:variables.bzl\", \"DEBUG_MODE\", \"USERS\")\nl"
  },
  {
    "path": "cc/config.bzl",
    "chars": 439,
    "preview": "CC_DISTROS = [\"debian12\", \"debian13\"]\nCC_ARCHITECTURES = {\n    \"debian12\": [\"amd64\", \"arm64\", \"arm\", \"s390x\", \"ppc64le\"]"
  },
  {
    "path": "common/BUILD.bazel",
    "chars": 3804,
    "preview": "load(\"@aspect_bazel_lib//lib:tar.bzl\", \"tar\")\nload(\"@rules_distroless//distroless:defs.bzl\", \"cacerts\", \"group\", \"home\","
  },
  {
    "path": "common/variables.bzl",
    "chars": 608,
    "preview": "\"common variables\"\n\ndef quote(str):\n    return '''\"{}\"'''.format(str)\n\nOS_RELEASE = dict(\n    PRETTY_NAME = \"Distroless\""
  },
  {
    "path": "cosign.pub",
    "chars": 178,
    "preview": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWZzVzkb8A+DbgDpaJId/bOmV8n7Q\nOqxYbK0Iro6GzSmOzxkn+N2AKawL"
  },
  {
    "path": "distro.bzl",
    "chars": 254,
    "preview": "VERSIONS = [\n    (\"debian12\", \"bookworm\", \"12\"),\n    (\"debian13\", \"trixie\", \"13\"),\n]\n\nVARIANTS = {\n    \"arm\": \"v7\",\n    "
  },
  {
    "path": "downloader.cfg",
    "chars": 140,
    "preview": "rewrite (mirrors\\.kernel\\.org)/gnu/(.*) https://ftp.gnu.org/gnu/$2\nrewrite (mirrors\\.kernel\\.org)/gnu/(.*) https://ftpmi"
  },
  {
    "path": "examples/BUILD",
    "chars": 54,
    "preview": "package(default_visibility = [\"//visibility:public\"])\n"
  },
  {
    "path": "examples/cc/BUILD",
    "chars": 1192,
    "preview": "# Public notice: this file is for internal documentation, testing, and\n# reference only. Note that repo maintainers can "
  },
  {
    "path": "examples/cc/Dockerfile",
    "chars": 161,
    "preview": "FROM gcc:6 AS build-env\nCOPY . /app\nWORKDIR /app\nRUN cc hello.c -o hello\n\nFROM gcr.io/distroless/cc\nCOPY --from=build-en"
  },
  {
    "path": "examples/cc/hello.c",
    "chars": 89,
    "preview": "#include <stdio.h>\n\nint main() {\n   printf(\"Hello from distroless C!\\n\");\n   return 0;\n}\n"
  },
  {
    "path": "examples/cc/hello_cc.cc",
    "chars": 108,
    "preview": "#include <iostream>\n\nint main() {\n   std::cout << \"Hello from distroless C++!\" << std::endl;\n   return 0;\n}\n"
  },
  {
    "path": "examples/cc/testdata/hello_cc_debian12.yaml",
    "chars": 152,
    "preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n  - name: hello_cc\n    command: ['/hello_cc_debian12_binary']\n    expectedOutput: ["
  },
  {
    "path": "examples/cc/testdata/hello_cc_debian13.yaml",
    "chars": 152,
    "preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n  - name: hello_cc\n    command: ['/hello_cc_debian13_binary']\n    expectedOutput: ["
  },
  {
    "path": "examples/cc/testdata/hello_debian12.yaml",
    "chars": 142,
    "preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n  - name: hello\n    command: ['/hello_debian12_binary']\n    expectedOutput: ['Hello"
  },
  {
    "path": "examples/cc/testdata/hello_debian13.yaml",
    "chars": 142,
    "preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n  - name: hello\n    command: ['/hello_debian13_binary']\n    expectedOutput: ['Hello"
  },
  {
    "path": "examples/go/BUILD",
    "chars": 638,
    "preview": "# Public notice: this file is for internal documentation, testing, and\n# reference only. Note that repo maintainers can "
  },
  {
    "path": "examples/go/Dockerfile",
    "chars": 235,
    "preview": "FROM golang:1.22 as build\n\nWORKDIR /go/src/app\nCOPY . .\n\nRUN go mod download\nRUN go vet -v\nRUN go test -v\n\nRUN CGO_ENABL"
  },
  {
    "path": "examples/go/go.mod",
    "chars": 71,
    "preview": "module github.com/GoogleContainerTools/distroless/examples/go\n\ngo 1.18\n"
  },
  {
    "path": "examples/go/main.go",
    "chars": 678,
    "preview": "// Copyright 2017 Google Inc. All rights reserved.\n\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n/"
  },
  {
    "path": "examples/go/main_test.go",
    "chars": 66,
    "preview": "package main\n\nimport (\n\t\"testing\"\n)\n\nfunc Test1(t *testing.T) {\n}\n"
  },
  {
    "path": "examples/java/BUILD",
    "chars": 1266,
    "preview": "# Public notice: this file is for internal documentation, testing, and\n# reference only. Note that repo maintainers can "
  },
  {
    "path": "examples/java/Dockerfile",
    "chars": 261,
    "preview": "FROM eclipse-temurin:17-jdk AS build-env\nCOPY . /app/examples\nWORKDIR /app\nRUN javac examples/*.java\nRUN jar cfe main.ja"
  },
  {
    "path": "examples/java/HelloJava.java",
    "chars": 140,
    "preview": "package examples;\n\npublic class HelloJava {\n    public static void main(String[] args) {\n        System.out.println(\"Hel"
  },
  {
    "path": "examples/java/testdata/hello_nonroot_debian13.yaml",
    "chars": 155,
    "preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n  - name: hello\n    command: ['/usr/bin/java', '-cp', '/*', 'examples.HelloJava']\n "
  },
  {
    "path": "examples/java/testdata/hello_root_debian13.yaml",
    "chars": 155,
    "preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n  - name: hello\n    command: ['/usr/bin/java', '-cp', '/*', 'examples.HelloJava']\n "
  },
  {
    "path": "examples/nodejs/BUILD",
    "chars": 1774,
    "preview": "# Public notice: this file is for internal documentation, testing, and\n# reference only. Note that repo maintainers can "
  },
  {
    "path": "examples/nodejs/Dockerfile",
    "chars": 178,
    "preview": "FROM node:22 AS build-env\nCOPY . /app\nWORKDIR /app\n\nRUN npm ci --omit=dev\n\nFROM gcr.io/distroless/nodejs22-debian13\nCOPY"
  },
  {
    "path": "examples/nodejs/hello.js",
    "chars": 29,
    "preview": "\nconsole.log(\"Hello World\");\n"
  },
  {
    "path": "examples/nodejs/hello_http.js",
    "chars": 482,
    "preview": "// Load the http module to create an http server.\nvar http = require('http');\n\n// Configure our HTTP server to respond w"
  },
  {
    "path": "examples/nodejs/node-express/Dockerfile",
    "chars": 201,
    "preview": "FROM node:22 AS build-env\nADD . /app\nWORKDIR /app\nRUN npm install --omit=dev\n\nFROM gcr.io/distroless/nodejs22-debian13\nC"
  },
  {
    "path": "examples/nodejs/node-express/hello_express.js",
    "chars": 208,
    "preview": "const express = require('express')\nconst app = express()\nconst port = 3000\n\napp.get('/', (req, res) => res.send('Hello W"
  },
  {
    "path": "examples/nodejs/node-express/package.json",
    "chars": 313,
    "preview": "{\n  \"name\": \"distroless-express\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Distroless express node.js\",\n  \"repository\": {"
  },
  {
    "path": "examples/nodejs/package.json",
    "chars": 209,
    "preview": "{\n  \"name\": \"nodejs\",\n  \"version\": \"1.0.0\",\n  \"description\": \"Example\",\n  \"main\": \"hello.js\",\n  \"scripts\": {\n    \"test\":"
  },
  {
    "path": "examples/nodejs/testdata/hello.yaml",
    "chars": 136,
    "preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n  - name: hello\n    command: [\"/nodejs/bin/node\", \"/hello.js\"]\n    expectedOutput: "
  },
  {
    "path": "examples/nonroot/BUILD",
    "chars": 1963,
    "preview": "# Public notice: this file is for internal documentation, testing, and\n# reference only. Note that repo maintainers can "
  },
  {
    "path": "examples/nonroot/testdata/user.go",
    "chars": 1180,
    "preview": "// Copyright 2017 Google Inc. All rights reserved.\n\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n/"
  },
  {
    "path": "examples/nonroot/testdata/user.yaml",
    "chars": 126,
    "preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n  - name: user\n    command: ['/user']\n    expectedOutput: ['User: nonroot', 'Uid: 1"
  },
  {
    "path": "examples/python3/BUILD",
    "chars": 1045,
    "preview": "# Public notice: this file is for internal documentation, testing, and\n# reference only. Note that repo maintainers can "
  },
  {
    "path": "examples/python3/Dockerfile",
    "chars": 159,
    "preview": "FROM python:3-slim AS build-env\nCOPY . /app\nWORKDIR /app\n\nFROM gcr.io/distroless/python3\nCOPY --from=build-env /app /app"
  },
  {
    "path": "examples/python3/hello.py",
    "chars": 1045,
    "preview": "#!/bin/python\n\n# Copyright 2017 Google Inc. All rights reserved.\n\n# Licensed under the Apache License, Version 2.0 (the "
  },
  {
    "path": "examples/python3-requirements/Dockerfile",
    "chars": 985,
    "preview": "# Build a virtualenv using the appropriate Debian release\n# * Install python3-venv for the built-in Python3 venv module "
  },
  {
    "path": "examples/python3-requirements/README.md",
    "chars": 1135,
    "preview": "# Python 3 with requirements.txt\n\nThis is a Python 3 application that specifies third-party dependencies using requireme"
  },
  {
    "path": "examples/python3-requirements/psutil_example.py",
    "chars": 514,
    "preview": "#!/usr/bin/env python3\n\n# Distroless's test.sh runs pylint on all python files, but this module will not exist\n# pylint:"
  },
  {
    "path": "examples/python3-requirements/requirements.txt",
    "chars": 85,
    "preview": "# this version of psutil does not ship binary wheels: must be compiled\npsutil==5.6.6\n"
  },
  {
    "path": "examples/rust/.dockerignore",
    "chars": 14,
    "preview": ".git/\ntarget/\n"
  },
  {
    "path": "examples/rust/BUILD",
    "chars": 599,
    "preview": "# Public notice: this file is for internal documentation, testing, and\n# reference only. Note that repo maintainers can "
  },
  {
    "path": "examples/rust/Cargo.toml",
    "chars": 129,
    "preview": "[package]\nname = \"hello-world-distroless\"\nversion = \"0.1.0\"\nauthors = [\"Bryant Hagadorn <blhagadorn@gmail.com>\"]\n\n[depen"
  },
  {
    "path": "examples/rust/Dockerfile",
    "chars": 212,
    "preview": "FROM rust:1 as build-env\nWORKDIR /app\nCOPY . /app\nRUN cargo build --release\n\nFROM gcr.io/distroless/cc-debian12\nCOPY --f"
  },
  {
    "path": "examples/rust/src/main.rs",
    "chars": 44,
    "preview": "fn main() {\n    println!(\"Hello World!\");\n}\n"
  },
  {
    "path": "examples/test.sh",
    "chars": 520,
    "preview": "#!/usr/bin/env bash\n\nset -euo pipefail\n\nfor d in examples/*; do\n\t# Skip non-directories.\n\tif [[ ! -d \"$d\" ]]; then conti"
  },
  {
    "path": "experimental/BUILD",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "experimental/busybox/BUILD",
    "chars": 345,
    "preview": "load(\"//:distro.bzl\", BUSYBOX_ARCHITECTURES = \"ALL_ARCHITECTURES\")\nload(\":commands.bzl\", \"busybox_layer\")\n\npackage(defau"
  },
  {
    "path": "experimental/busybox/commands.bzl",
    "chars": 5978,
    "preview": "load(\"@aspect_bazel_lib//lib:tar.bzl\", \"tar\")\n\nBUSYBOX_COMMANDS = [\n    \"[\",\n    \"[[\",\n    \"acpid\",\n    \"add-shell\",\n   "
  },
  {
    "path": "java/BUILD",
    "chars": 1051,
    "preview": "load(\":config.bzl\", \"JAVA_ARCHITECTURES\", \"JAVA_BASE_PACKAGES\", \"JAVA_DISTROS\")\nload(\":java.bzl\", \"java_base_image\", \"ja"
  },
  {
    "path": "java/README.md",
    "chars": 1073,
    "preview": "# Documentation for `gcr.io/distroless/java`\n\n## Image Contents\n\nThis image contains a minimal Linux, OpenJDK-based runt"
  },
  {
    "path": "java/config.bzl",
    "chars": 663,
    "preview": "JAVA_DISTROS = [\"debian13\"]\nJAVA_ARCHITECTURES = {\n    \"debian13\": [\"amd64\", \"arm64\", \"s390x\", \"ppc64le\", \"riscv64\"],\n}\n"
  },
  {
    "path": "java/control",
    "chars": 270,
    "preview": "Package: Eclipse Temurin\nVersion: {{VERSION}}\nArchitecture: {{ARCHITECTURE}}\nMaintainer: Adoptium Working Group <https:/"
  },
  {
    "path": "java/java.bzl",
    "chars": 10092,
    "preview": "\"java image definitions\"\n\nload(\"@container_structure_test//:defs.bzl\", \"container_structure_test\")\nload(\"@rules_oci//oci"
  },
  {
    "path": "java/jre_ver.bzl",
    "chars": 468,
    "preview": "def jre_ver(version):\n    \"\"\"Extract JRE version from a Debian openjdk downloaded filename.\n\n      Debian packages versi"
  },
  {
    "path": "java/testdata/CheckCerts.java",
    "chars": 489,
    "preview": "package testdata;\n\nimport java.io.IOException;\nimport java.net.URL;\nimport javax.net.ssl.HttpsURLConnection;\n\npublic cla"
  },
  {
    "path": "java/testdata/CheckEncoding.java",
    "chars": 530,
    "preview": "package testdata;\n\nimport java.nio.charset.Charset;\nimport java.util.Locale;\n\npublic class CheckEncoding {\n\n  public sta"
  },
  {
    "path": "java/testdata/CheckLibharfbuzz.java",
    "chars": 403,
    "preview": "package testdata;\n\nimport java.awt.GraphicsEnvironment;\nimport java.awt.Font;\n\npublic class CheckLibharfbuzz {\n    publi"
  },
  {
    "path": "java/testdata/java17_debian13.yaml",
    "chars": 1227,
    "preview": "schemaVersion: \"2.0.0\"\ncommandTests:\n  - name: java\n    command: \"/usr/lib/jvm/temurin-17-jre-amd64/bin/java\"\n    args: "
  },
  {
    "path": "java/testdata/java17_debug_debian13.yaml",
    "chars": 987,
    "preview": "schemaVersion: \"2.0.0\"\ncommandTests:\n  - name: java\n    command: \"/usr/lib/jvm/temurin-17-jdk-amd64/bin/java\"\n    args: "
  },
  {
    "path": "java/testdata/java21_debian13.yaml",
    "chars": 1229,
    "preview": "schemaVersion: \"2.0.0\"\ncommandTests:\n  - name: java\n    command: \"/usr/lib/jvm/temurin-21-jre-amd64/bin/java\"\n    args: "
  },
  {
    "path": "java/testdata/java21_debug_debian13.yaml",
    "chars": 987,
    "preview": "schemaVersion: \"2.0.0\"\ncommandTests:\n  - name: java\n    command: \"/usr/lib/jvm/temurin-21-jdk-amd64/bin/java\"\n    args: "
  },
  {
    "path": "java/testdata/java25_debian13.yaml",
    "chars": 1226,
    "preview": "schemaVersion: \"2.0.0\"\ncommandTests:\n  - name: java\n    command: \"/usr/lib/jvm/temurin-25-jre-amd64/bin/java\"\n    args: "
  },
  {
    "path": "java/testdata/java25_debug_debian13.yaml",
    "chars": 983,
    "preview": "schemaVersion: \"2.0.0\"\ncommandTests:\n  - name: java\n    command: \"/usr/lib/jvm/temurin-25-jdk-amd64/bin/java\"\n    args: "
  },
  {
    "path": "java/testdata/java_base.yaml",
    "chars": 316,
    "preview": "schemaVersion: \"2.0.0\"\nfileExistenceTests:\n  - name: certs\n    path: \"/etc/ssl/certs/java/cacerts\"\n    shouldExist: true"
  },
  {
    "path": "java/testdata/java_base_debug.yaml",
    "chars": 312,
    "preview": "schemaVersion: \"2.0.0\"\nfileExistenceTests:\n  - name: certs\n    path: \"/etc/ssl/certs/java/cacerts\"\n    shouldExist: true"
  },
  {
    "path": "java/testdata/java_certs.yaml",
    "chars": 324,
    "preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n  - name: connect_to_https_google_com\n    # This is a bit ugly because structure te"
  },
  {
    "path": "java/testdata/java_encoding.yaml",
    "chars": 408,
    "preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n  - name: check_encoding\n    command: [\"/usr/bin/java\",\n              \"-cp\",\n      "
  },
  {
    "path": "java/testdata/java_libharfbuzz.yaml",
    "chars": 225,
    "preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n  - name: check_libharfbuzz\n    command: [\"/usr/bin/java\",\n              \"-cp\",\n   "
  },
  {
    "path": "knife",
    "chars": 7751,
    "preview": "#!/usr/bin/env bash\nset -o pipefail -o errexit -o nounset\n\n# Copyright 2024 Google Inc. All rights reserved.\n\n# Licensed"
  },
  {
    "path": "knife.d/update_java_versions.sh",
    "chars": 1251,
    "preview": "set -o pipefail -o errexit -o nounset\n\n# Copyright 2024 Google Inc. All rights reserved.\n\n# Licensed under the Apache Li"
  },
  {
    "path": "knife.d/update_node_archives.js",
    "chars": 6417,
    "preview": "#!/usr/bin/env node\nconst crypto = require(\"crypto\");\nconst https = require(\"https\");\nconst fs = require(\"fs\");\n\nif (pro"
  },
  {
    "path": "nodejs/BUILD",
    "chars": 702,
    "preview": "load(\":config.bzl\", \"NODEJS_ARCHITECTURES\", \"NODEJS_DISTROS\", \"NODEJS_MAJOR_VERSIONS\")\nload(\":nodejs.bzl\", \"nodejs_image"
  },
  {
    "path": "nodejs/README.md",
    "chars": 764,
    "preview": "# Documentation for `gcr.io/distroless/nodejs`\n\n## Image Contents\n\nThese images contain a minimal Linux, Node.js-based r"
  },
  {
    "path": "nodejs/config.bzl",
    "chars": 301,
    "preview": "NODEJS_DISTROS = [\"debian13\"]\nNODEJS_ARCHITECTURES = {\n    \"debian13\": {\n        \"20\": [\"amd64\", \"arm64\", \"arm\", \"s390x\""
  },
  {
    "path": "nodejs/control",
    "chars": 681,
    "preview": "Package: nodejs\nVersion: {{VERSION}}\nArchitecture: {{ARCHITECTURE}}\nMaintainer: OpenJS Foundation <https://openjsf.org/>"
  },
  {
    "path": "nodejs/nodejs.bzl",
    "chars": 3336,
    "preview": "\"nodejs image definitions\"\n\nload(\"@container_structure_test//:defs.bzl\", \"container_structure_test\")\nload(\"@rules_oci//o"
  },
  {
    "path": "nodejs/testdata/check_certificate.js",
    "chars": 244,
    "preview": "const https = require('https')\nconst options = {\n  hostname: 'www.google.com',\n  port: 443,\n  path: '/',\n  method: 'GET'"
  },
  {
    "path": "nodejs/testdata/check_certificate.yaml",
    "chars": 163,
    "preview": "schemaVersion: \"2.0.0\"\ncommandTests:\n  - name: nodejs\n    command: \"/nodejs/bin/node\" \n    args: [\"/check_certificate.js"
  },
  {
    "path": "nodejs/testdata/check_headers.yaml",
    "chars": 120,
    "preview": "schemaVersion: \"2.0.0\"\nfileExistenceTests:\n  - name: npm\n    path: '/nodejs/include/node/node.h'\n    shouldExist: false\n"
  },
  {
    "path": "nodejs/testdata/check_npm.yaml",
    "chars": 414,
    "preview": "schemaVersion: \"2.0.0\"\nfileExistenceTests:\n  - name: npm\n    path: '/nodejs/lib/node_modules/npm'\n    shouldExist: false"
  },
  {
    "path": "nodejs/testdata/nodejs20.yaml",
    "chars": 143,
    "preview": "schemaVersion: \"2.0.0\"\ncommandTests:\n  - name: nodejs\n    command: \"/nodejs/bin/node\"\n    args: [\"--version\"]\n    expect"
  },
  {
    "path": "nodejs/testdata/nodejs22.yaml",
    "chars": 143,
    "preview": "schemaVersion: \"2.0.0\"\ncommandTests:\n  - name: nodejs\n    command: \"/nodejs/bin/node\"\n    args: [\"--version\"]\n    expect"
  },
  {
    "path": "nodejs/testdata/nodejs24.yaml",
    "chars": 143,
    "preview": "schemaVersion: \"2.0.0\"\ncommandTests:\n  - name: nodejs\n    command: \"/nodejs/bin/node\"\n    args: [\"--version\"]\n    expect"
  },
  {
    "path": "private/extensions/BUILD.bazel",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "private/extensions/busybox.bzl",
    "chars": 5833,
    "preview": "\"busybox\"\n\nload(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_archive\", \"http_file\")\nload(\"//experimental/busybo"
  },
  {
    "path": "private/extensions/node.bzl",
    "chars": 8938,
    "preview": "\"node\"\n\nBUILD_TMPL = \"\"\"\\\n# GENERATED BY node_archive.bzl\nload(\"@distroless//private/pkg:debian_spdx.bzl\", \"debian_spdx\""
  },
  {
    "path": "private/extensions/version.bzl",
    "chars": 2232,
    "preview": "\"generates version information from lockfiles\"\n\n# buildifier: disable=bzl-visibility\nload(\"@rules_distroless//apt/privat"
  },
  {
    "path": "private/oci/BUILD.bazel",
    "chars": 40,
    "preview": "exports_files([\"sign_and_push.sh.tpl\"])\n"
  },
  {
    "path": "private/oci/cc_image.bzl",
    "chars": 626,
    "preview": "\"cc_image rule for creating C++ container images\"\n\nload(\"@rules_cc//cc:cc_binary.bzl\", \"cc_binary\")\nload(\"@rules_oci//oc"
  },
  {
    "path": "private/oci/defs.bzl",
    "chars": 397,
    "preview": "load(\":cc_image.bzl\", _cc_image = \"cc_image\")\nload(\":go_image.bzl\", _go_image = \"go_image\")\nload(\":java_image.bzl\", _jav"
  },
  {
    "path": "private/oci/digest.bzl",
    "chars": 1386,
    "preview": "\"generate digest for oci_image and oci_image_index\"\n\nload(\"@aspect_bazel_lib//lib:copy_file.bzl\", \"copy_file\")\nload(\"@as"
  },
  {
    "path": "private/oci/go_image.bzl",
    "chars": 694,
    "preview": "\"go_image rule for creating Go container images\"\n\nload(\"@rules_go//go:def.bzl\", \"go_binary\")\nload(\"@rules_oci//oci:defs."
  },
  {
    "path": "private/oci/java_image.bzl",
    "chars": 633,
    "preview": "\"java_image rule for creating Java container images\"\n\nload(\"@rules_oci//oci:defs.bzl\", \"oci_image\")\nload(\"//private/util"
  },
  {
    "path": "private/oci/rust_image.bzl",
    "chars": 703,
    "preview": "\"rust_image rule for creating Rust container images\"\n\nload(\"@rules_oci//oci:defs.bzl\", \"oci_image\")\nload(\"@rules_rust//r"
  },
  {
    "path": "private/oci/sign_and_push.bzl",
    "chars": 5352,
    "preview": "\"rules for signing, attesting and pushing images\"\n\nload(\"@bazel_skylib//rules:write_file.bzl\", \"write_file\")\nload(\"//pri"
  },
  {
    "path": "private/oci/sign_and_push.sh.tpl",
    "chars": 765,
    "preview": "#!/usr/bin/env bash\nset -o pipefail -o errexit -o nounset\n\nKEYLESS=\"${KEYLESS:-}\"\n\nwhile (( $# > 0 )); do\n  case $1 in\n "
  },
  {
    "path": "private/pkg/BUILD.bazel",
    "chars": 592,
    "preview": "load(\"@rules_go//go:def.bzl\", \"go_binary\")\n\npackage(default_visibility = [\"//visibility:public\"])\n\ngo_binary(\n    name ="
  },
  {
    "path": "private/pkg/debian_spdx.bzl",
    "chars": 1991,
    "preview": "SPDX_CMD = \"\"\"\\\ntmp=\"$(mktemp -d)\"\n\ntar -xf \"$1\" -C \"$tmp\" \"./control\" || tar -xf \"$1\" -C \"$tmp\" \"control\"\n\nif  tar -xf "
  },
  {
    "path": "private/pkg/debian_spdx.go",
    "chars": 4474,
    "preview": "package main\n\nimport (\n\t\"bufio\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n\n\tspdx_json \"github.com/spdx/tool"
  },
  {
    "path": "private/pkg/oci_image_spdx.bzl",
    "chars": 2006,
    "preview": "\"an aspect rule that generates spdx by collecting spdx output group\"\n\ndef _image_aspect_impl(target, ctx):\n    output = "
  },
  {
    "path": "private/pkg/oci_image_spdx.go",
    "chars": 3538,
    "preview": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\n\tspdx_json \"github.com/spdx/tools-golang/json\"\n\t\"github.c"
  },
  {
    "path": "private/pkg/test/oci_image/BUILD.bazel",
    "chars": 1135,
    "preview": "load(\"@aspect_bazel_lib//lib:write_source_files.bzl\", \"write_source_files\")\nload(\"@rules_oci//oci:defs.bzl\", \"oci_image\""
  },
  {
    "path": "private/pkg/test/oci_image/fat_image_sbom.spdx.json",
    "chars": 7918,
    "preview": "{\"spdxVersion\":\"SPDX-2.3\",\"dataLicense\":\"CC0-1.0\",\"SPDXID\":\"SPDXRef-DOCUMENT\",\"name\":\"//private/pkg/test/oci_image:fat_i"
  },
  {
    "path": "private/pkg/test/oci_image/image_amd64.spdx.json",
    "chars": 3790,
    "preview": "{\"spdxVersion\":\"SPDX-2.3\",\"dataLicense\":\"CC0-1.0\",\"SPDXID\":\"SPDXRef-DOCUMENT\",\"name\":\"//private/pkg/test/oci_image:image"
  },
  {
    "path": "private/pkg/test/oci_image/image_arm64.spdx.json",
    "chars": 3966,
    "preview": "{\"spdxVersion\":\"SPDX-2.3\",\"dataLicense\":\"CC0-1.0\",\"SPDXID\":\"SPDXRef-DOCUMENT\",\"name\":\"//private/pkg/test/oci_image:image"
  },
  {
    "path": "private/repos/deb/BUILD.bazel",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "private/repos/deb/bookworm.lock.json",
    "chars": 24525,
    "preview": "{\n\t\"packages\": [\n\t\t{\n\t\t\t\"arch\": \"amd64\",\n\t\t\t\"dependencies\": [],\n\t\t\t\"key\": \"base-files_12.4-p-deb12u13_amd64\",\n\t\t\t\"name\":"
  },
  {
    "path": "private/repos/deb/bookworm.yaml",
    "chars": 605,
    "preview": "# debian 12\nversion: 1\n\nsources:\n  - channel: bookworm main\n    url: https://snapshot.debian.org/archive/debian/20260314"
  },
  {
    "path": "private/repos/deb/bookworm_python.lock.json",
    "chars": 18065,
    "preview": "{\n\t\"packages\": [\n\t\t{\n\t\t\t\"arch\": \"amd64\",\n\t\t\t\"dependencies\": [],\n\t\t\t\"key\": \"libbz2-1.0_1.0.8-5-p-b1_amd64\",\n\t\t\t\"name\": \"l"
  },
  {
    "path": "private/repos/deb/bookworm_python.yaml",
    "chars": 877,
    "preview": "# debian 12, limited architectures, python only\nversion: 1\n\nsources:\n  - channel: bookworm main\n    url: https://snapsho"
  },
  {
    "path": "private/repos/deb/deb.MODULE.bazel",
    "chars": 1055,
    "preview": "\"debian dependencies\"\n\nREPOS = [\n    \"trixie\",\n    \"trixie_java\",\n    \"trixie_adoptium\",\n    \"trixie_python\",\n    \"bookw"
  },
  {
    "path": "private/repos/deb/package.BUILD.tmpl",
    "chars": 1022,
    "preview": "\"\"\"Generated by distroless. DO NOT EDIT!\"\"\"\n\nload(\"@@//private/pkg:debian_spdx.bzl\", \"debian_spdx\")\nload(\"@@//private/ut"
  },
  {
    "path": "private/repos/deb/trixie.lock.json",
    "chars": 35457,
    "preview": "{\n\t\"packages\": [\n\t\t{\n\t\t\t\"arch\": \"amd64\",\n\t\t\t\"dependencies\": [],\n\t\t\t\"key\": \"base-files_13.8-p-deb13u4_amd64\",\n\t\t\t\"name\": "
  },
  {
    "path": "private/repos/deb/trixie.yaml",
    "chars": 656,
    "preview": "# debian 13\nversion: 1\n\nsources:\n  - channel: trixie main\n    url: https://snapshot.debian.org/archive/debian/20260314T2"
  },
  {
    "path": "private/repos/deb/trixie_adoptium.lock.json",
    "chars": 11537,
    "preview": "{\n\t\"packages\": [\n\t\t{\n\t\t\t\"arch\": \"amd64\",\n\t\t\t\"dependencies\": [],\n\t\t\t\"key\": \"temurin-17-jre_17.0.18.0.0-p-8-1_amd64\",\n\t\t\t\""
  },
  {
    "path": "private/repos/deb/trixie_adoptium.yaml",
    "chars": 346,
    "preview": "# debian 13, java from adoptium repositories\nversion: 1\n\nsources:\n  # adoptium\n  - channel: trixie main\n    url: https:/"
  },
  {
    "path": "private/repos/deb/trixie_java.lock.json",
    "chars": 37596,
    "preview": "{\n\t\"packages\": [\n\t\t{\n\t\t\t\"arch\": \"amd64\",\n\t\t\t\"dependencies\": [],\n\t\t\t\"key\": \"fontconfig-config_2.15.0-2.3_amd64\",\n\t\t\t\"name"
  },
  {
    "path": "private/repos/deb/trixie_java.yaml",
    "chars": 881,
    "preview": "# debian 13, limited architectures, java only\nversion: 1\n\nsources:\n  - channel: trixie main\n    url: https://snapshot.de"
  },
  {
    "path": "private/repos/deb/trixie_python.lock.json",
    "chars": 18501,
    "preview": "{\n\t\"packages\": [\n\t\t{\n\t\t\t\"arch\": \"amd64\",\n\t\t\t\"dependencies\": [],\n\t\t\t\"key\": \"libbz2-1.0_1.0.8-6_amd64\",\n\t\t\t\"name\": \"libbz2"
  },
  {
    "path": "private/repos/deb/trixie_python.yaml",
    "chars": 891,
    "preview": "# debian 13, limited architectures, python only\nversion: 1\n\nsources:\n  - channel: trixie main\n    url: https://snapshot."
  },
  {
    "path": "private/stamp.bash",
    "chars": 512,
    "preview": "#!/usr/bin/env bash\nset -o pipefail -o errexit -o nounset\n\n# Do not error if the user doesn't have gcloud installed.\nif "
  },
  {
    "path": "private/tools/diff/BUILD.bazel",
    "chars": 288,
    "preview": "sh_binary(\n    name = \"diff\",\n    srcs = [\"diff.bash\"],\n    args = [\n        \"--head-ref\",\n        \"test\",\n        \"--ba"
  },
  {
    "path": "private/tools/diff/diff.bash",
    "chars": 6011,
    "preview": "#!/usr/bin/env bash\nset -o pipefail -o errexit -o nounset\n\n# ./private/tools/diff/diff.bash --head-ref test --base-ref t"
  },
  {
    "path": "private/tools/lifecycle/BUILD.bazel",
    "chars": 74,
    "preview": "\"rules to deal with auto_vm lifecycle tagging\"\n\nexports_files([\"tag.sh\"])\n"
  },
  {
    "path": "private/tools/lifecycle/defs.bzl",
    "chars": 115,
    "preview": "load(\":tag.bzl\", _attach_lifecycle_tags = \"attach_lifecycle_tags\")\n\nattach_lifecycle_tags = _attach_lifecycle_tags\n"
  },
  {
    "path": "private/tools/lifecycle/tag.bzl",
    "chars": 2102,
    "preview": "\"rules for attaching lifecycle tags to older published images\"\n\nload(\"@bazel_skylib//rules:write_file.bzl\", \"write_file\""
  },
  {
    "path": "private/tools/lifecycle/tag.sh",
    "chars": 3446,
    "preview": "#!/usr/bin/env bash\nset -euo pipefail\n\ntrap 'echo \"Ctrl+C caught, exiting...\"; exit 130' INT\n\nfunction exec_tag() {\n  lo"
  },
  {
    "path": "private/tools/lifecycle/tag.sh.README.md",
    "chars": 1801,
    "preview": "# Container Image Lifecycle Tagger (Distroless Admin Tooling)\n\nA Bash utility to bulk-tag container images in a registry"
  },
  {
    "path": "private/util/BUILD",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "private/util/deb.bzl",
    "chars": 1364,
    "preview": "\"utility functions for constructing debian package labels\"\n\nload(\"@versions//:versions.bzl\", \"version\")\n\nDIST_ALIAS = di"
  },
  {
    "path": "private/util/extract.bzl",
    "chars": 1105,
    "preview": "\"a utility that extracts files from tar\"\n\nload(\"@aspect_bazel_lib//lib:tar.bzl\", \"tar_lib\")\n\ndef _tar_extract_file_impl("
  },
  {
    "path": "private/util/java_cacerts.bzl",
    "chars": 426,
    "preview": "\"java ca certificates\"\n\nload(\"@rules_distroless//distroless:defs.bzl\", \"java_keystore\")\nload(\":extract.bzl\", \"tar_extrac"
  },
  {
    "path": "private/util/merge_providers.bzl",
    "chars": 1098,
    "preview": "\"a utility rule that merges DefaultInfo and OutputGroupInfo providers\"\n\nSKIP = \"_hidden_top_level_INTERNAL_\"\n\ndef _impl("
  },
  {
    "path": "private/util/tar.bzl",
    "chars": 2022,
    "preview": "\"Wrapper around tar rules for creating tar archives\"\n\nload(\"@aspect_bazel_lib//lib:tar.bzl\", _aspect_tar = \"tar\")\nload(\""
  },
  {
    "path": "python3/BUILD",
    "chars": 1187,
    "preview": "load(\"//private/util:tar.bzl\", \"tar\")\nload(\":config.bzl\", \"PYTHON_ARCHITECTURES\", \"PYTHON_DISTROS\", \"PYTHON_PACKAGES\")\nl"
  },
  {
    "path": "python3/README.md",
    "chars": 799,
    "preview": "# Documentation for `gcr.io/distroless/python3`\n\n## Image Contents\n\nThis image contains a minimal Linux, Python-based ru"
  },
  {
    "path": "python3/config.bzl",
    "chars": 1371,
    "preview": "PYTHON_DISTROS = [\"debian12\", \"debian13\"]\nPYTHON_ARCHITECTURES = {\n    \"debian12\": [\"amd64\", \"arm64\"],\n    \"debian13\": ["
  },
  {
    "path": "python3/ldconfig/ldconfig.sh",
    "chars": 1644,
    "preview": "#!/usr/bin/env bash\nset -eo pipefail\n\nMODE=$1\nshift\n\nif [[ \"${MODE}\" != \"update\" && \"${MODE}\" != \"check\" ]]; then\n  echo"
  },
  {
    "path": "python3/ldconfig.bzl",
    "chars": 2426,
    "preview": "\"Macros for generating ldconfig cache for python images\"\n\nload(\"@rules_oci//oci:defs.bzl\", \"oci_load\")\n\ndef python3_ldco"
  },
  {
    "path": "python3/python.bzl",
    "chars": 3139,
    "preview": "\"python3 image definitions\"\n\nload(\"@container_structure_test//:defs.bzl\", \"container_structure_test\")\nload(\"@rules_oci//"
  },
  {
    "path": "python3/testdata/debian12.yaml",
    "chars": 2349,
    "preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n  - name: version\n    command: [\"/usr/bin/python3\", \"--version\"]\n    expectedOutput"
  },
  {
    "path": "python3/testdata/debian13.yaml",
    "chars": 2281,
    "preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n  - name: version\n    command: [\"/usr/bin/python3\", \"--version\"]\n    expectedOutput"
  },
  {
    "path": "python3/testdata/python3.yaml",
    "chars": 830,
    "preview": "schemaVersion: \"1.0.0\"\ncommandTests:\n  - name: hello\n    command: [\"/usr/bin/python3\", \"-c\", \"print('Hello World')\"]\n   "
  },
  {
    "path": "static/BUILD",
    "chars": 925,
    "preview": "load(\"//private/util:tar.bzl\", \"tar\")\nload(\":config.bzl\", \"STATIC_ARCHITECTURES\", \"STATIC_DISTROS\", \"STATIC_PACKAGES\")\nl"
  },
  {
    "path": "static/config.bzl",
    "chars": 468,
    "preview": "STATIC_DISTROS = [\"debian12\", \"debian13\"]\nSTATIC_ARCHITECTURES = {\n    \"debian12\": [\"amd64\", \"arm64\", \"arm\", \"s390x\", \"p"
  },
  {
    "path": "static/nsswitch.conf",
    "chars": 497,
    "preview": "# /etc/nsswitch.conf\n#\n# Example configuration of GNU Name Service Switch functionality.\n# If you have the `glibc-doc-re"
  },
  {
    "path": "static/static.bzl",
    "chars": 5435,
    "preview": "\"defines a function to build static different distributions\"\n\nload(\"@container_structure_test//:defs.bzl\", \"container_st"
  },
  {
    "path": "static/testdata/check_certs.go",
    "chars": 894,
    "preview": "// Copyright 2017 Google Inc. All rights reserved.\n\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n/"
  },
  {
    "path": "static/testdata/debian12.yaml",
    "chars": 170,
    "preview": "schemaVersion: \"1.0.0\"\nfileContentTests:\n- name: 'os-release contents'\n  path: '/etc/os-release'\n  expectedContents: ['."
  },
  {
    "path": "static/testdata/debian13.yaml",
    "chars": 168,
    "preview": "schemaVersion: \"1.0.0\"\nfileContentTests:\n- name: 'os-release contents'\n  path: '/etc/os-release'\n  expectedContents: ['."
  }
]

// ... and 4 more files (download for full content)

About this extraction

This page contains the full source code of the GoogleContainerTools/distroless GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 204 files (402.4 KB), approximately 145.1k tokens, and a symbol index with 26 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!