Full Code of adobe/rules_gitops for AI

main 6387a44155b9 cached
251 files
462.8 KB
138.1k tokens
164 symbols
1 requests
Download .txt
Showing preview only (520K chars total). Download the full file or copy to clipboard to get everything.
Repository: adobe/rules_gitops
Branch: main
Commit: 6387a44155b9
Files: 251
Total size: 462.8 KB

Directory structure:
gitextract_s1k_48ok/

├── .bazelignore
├── .bazelrc
├── .bazelversion
├── .bcr/
│   ├── metadata.template.json
│   ├── presubmit.yml
│   └── source.template.json
├── .gitattributes
├── .github/
│   ├── CODEOWNERS
│   ├── CONTRIBUTING.md
│   ├── ISSUE_TEMPLATE.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   └── workflows/
│       ├── ci.yaml
│       ├── conventional-commits.yaml
│       ├── publish.yaml
│       ├── release.yml
│       └── release_prep.sh
├── .gitignore
├── BUILD.bazel
├── CODE_OF_CONDUCT.md
├── COPYRIGHT
├── LICENSE
├── MODULE.bazel
├── README.md
├── WORKSPACE.bazel
├── adapters/
│   ├── BUILD.bazel
│   ├── external_image.bzl
│   ├── providers.bzl
│   └── rules_img.bzl
├── e2e/
│   ├── BUILD.bazel
│   ├── deployment/
│   │   ├── BUILD.bazel
│   │   ├── helloworld.go
│   │   └── manifests/
│   │       ├── deployment.yaml
│   │       └── service.yaml
│   └── util/
│       ├── BUILD.bazel
│       ├── kubectl_cmd.sh.tpl
│       ├── setup.sh
│       ├── teardown.sh
│       ├── test.sh
│       └── util.bzl
├── examples/
│   ├── README.md
│   ├── helloworld/
│   │   ├── .bazelrc
│   │   ├── .bazelversion
│   │   ├── BUILD.bazel
│   │   ├── MODULE.bazel
│   │   ├── deployment.yaml
│   │   ├── helloworld.go
│   │   ├── helloworld_test.go
│   │   ├── k8s_deploy_test.sh
│   │   └── service.yaml
│   └── legacy_docker/
│       ├── .bazelrc
│       ├── .bazelversion
│       ├── BUILD.bazel
│       ├── MODULE.bazel
│       ├── WORKSPACE
│       ├── adapters/
│       │   ├── BUILD.bazel
│       │   └── docker.bzl
│       ├── container_contents.txt
│       ├── deployment.yaml
│       └── patches/
│           └── docker.patch
├── gitops/
│   ├── BUILD.bazel
│   ├── analysis/
│   │   ├── BUILD.bazel
│   │   ├── analysis.go
│   │   └── analysis.proto
│   ├── bazel/
│   │   ├── BUILD.bazel
│   │   ├── bazeltargets.go
│   │   └── bazeltargets_test.go
│   ├── blaze_query/
│   │   ├── BUILD.bazel
│   │   └── build.proto
│   ├── commitmsg/
│   │   ├── BUILD.bazel
│   │   ├── commitmsg.go
│   │   └── commitmsg_test.go
│   ├── defs.bzl
│   ├── digester/
│   │   ├── BUILD.bazel
│   │   └── digester.go
│   ├── exec/
│   │   ├── BUILD
│   │   ├── BUILD.bazel
│   │   └── exec.go
│   ├── git/
│   │   ├── BUILD.bazel
│   │   ├── bitbucket/
│   │   │   ├── BUILD.bazel
│   │   │   ├── bitbucket.go
│   │   │   ├── bitbucket_test.go
│   │   │   └── testdata/
│   │   │       └── create_pr.json
│   │   ├── git.go
│   │   ├── github/
│   │   │   ├── BUILD.bazel
│   │   │   └── github.go
│   │   ├── gitlab/
│   │   │   ├── BUILD.bazel
│   │   │   ├── gitlab.go
│   │   │   └── gitlab_test.go
│   │   └── server.go
│   ├── prer/
│   │   ├── BUILD.bazel
│   │   └── create_gitops_prs.go
│   └── private/
│       ├── BUILD.bazel
│       ├── gitops.bzl
│       ├── k8s_deploy.bzl
│       ├── k8s_gitops.sh.tpl
│       ├── nameprefix_deployment_labels_config.yaml
│       ├── namesuffix_deployment_labels_config.yaml
│       └── test/
│           ├── BUILD.bazel
│           ├── deployment.yaml
│           ├── deployment1.yaml
│           ├── goldens/
│           │   ├── deploy_test.golden
│           │   └── external_image_test.golden
│           ├── images/
│           │   ├── BUILD.bazel
│           │   └── container_content.txt
│           └── templates.bzl
├── go.mod
├── go.sum
├── kubectl/
│   ├── BUILD.bazel
│   ├── defs.bzl
│   └── private/
│       ├── BUILD.bazel
│       ├── extension.bzl
│       ├── kubeconfig.bzl
│       ├── kubectl_binary.bzl
│       ├── platforms.bzl
│       ├── providers.bzl
│       ├── resolved_toolchain.bzl
│       ├── run-all.sh.tpl
│       ├── toolchain.bzl
│       └── versions/
│           ├── BUILD.bazel
│           ├── update_versions.go
│           └── versions.bzl
├── kustomize/
│   ├── BUILD.bazel
│   ├── defs.bzl
│   └── private/
│       ├── BUILD.bazel
│       ├── extension.bzl
│       ├── kustomization.bzl
│       ├── kustomize_binary.bzl
│       ├── platforms.bzl
│       ├── providers.bzl
│       ├── resolved_toolchain.bzl
│       ├── show.bzl
│       ├── tests/
│       │   ├── BUILD.bazel
│       │   ├── crb.yaml
│       │   ├── deployment.yaml
│       │   ├── goldens/
│       │   │   └── raw.golden
│       │   └── service.yaml
│       ├── toolchain.bzl
│       └── versions/
│           ├── BUILD.bazel
│           ├── update_versions.go
│           └── versions.bzl
├── renovate.json
├── resolver/
│   ├── BUILD.bazel
│   ├── pkg/
│   │   ├── BUILD.bazel
│   │   ├── resolver.go
│   │   ├── resolver_test.go
│   │   └── testdata/
│   │       ├── cwf.expected.yaml
│   │       ├── cwf.yaml
│   │       ├── digest.expected.yaml
│   │       ├── digest.yaml
│   │       ├── emptyinit.expected.yaml
│   │       ├── emptyinit.yaml
│   │       ├── flinkapp.expected.yaml
│   │       ├── flinkapp.yaml
│   │       ├── happypath.expected.yaml
│   │       ├── happypath.yaml
│   │       ├── zk.expected.yaml
│   │       └── zk.yaml
│   └── resolver.go
├── stamper/
│   ├── BUILD.bazel
│   ├── main.go
│   └── stamp.bzl
├── templating/
│   ├── BUILD.bazel
│   ├── fasttemplate/
│   │   ├── BUILD.bazel
│   │   ├── LICENSE
│   │   ├── README.md
│   │   ├── example_test.go
│   │   ├── template.go
│   │   └── template_test.go
│   ├── main.go
│   └── testdata/
│       ├── generated
│       ├── stable-status.txt
│       ├── template1.tpl
│       └── volatile-status.txt
├── testing/
│   ├── BUILD.bazel
│   ├── defs.bzl
│   ├── it_manifest_filter/
│   │   ├── BUILD.bazel
│   │   ├── it_manifest_filter.go
│   │   └── pkg/
│   │       ├── BUILD.bazel
│   │       ├── filter.go
│   │       ├── filter_test.go
│   │       └── testdata/
│   │           ├── certificate.expected.yaml
│   │           ├── certificate.yaml
│   │           ├── happypath.expected.yaml
│   │           ├── happypath.yaml
│   │           ├── statefulset.expected.yaml
│   │           ├── statefulset.yaml
│   │           ├── statefulset2.expected.yaml
│   │           ├── statefulset2.yaml
│   │           ├── statefulset3.expected.yaml
│   │           └── statefulset3.yaml
│   ├── it_sidecar/
│   │   ├── BUILD.bazel
│   │   ├── client/
│   │   │   ├── BUILD.bazel
│   │   │   ├── noop.setup
│   │   │   ├── sidecar_client.go
│   │   │   ├── test_callback/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   └── sidecar_client_test.go
│   │   │   └── test_no_callback/
│   │   │       ├── BUILD.bazel
│   │   │       └── sidecar_client_test_no_callback.go
│   │   ├── it_sidecar.go
│   │   └── stern/
│   │       ├── BUILD.bazel
│   │       ├── container_state.go
│   │       ├── main.go
│   │       ├── tail.go
│   │       └── watch.go
│   └── private/
│       ├── BUILD.bazel
│       ├── k8s_test_namespace.bzl
│       ├── k8s_test_namespace.sh.tpl
│       ├── k8s_test_setup.bzl
│       └── set_namespace.sh
├── tests/
│   ├── BUILD.bazel
│   ├── configmaps/
│   │   ├── mapa/
│   │   │   ├── mapa1.properties
│   │   │   └── mapa2.properties
│   │   └── mapb/
│   │       └── config.yaml
│   ├── crb.yaml
│   ├── deployment.yaml
│   ├── deployment_with_labels.yaml
│   ├── expected_image_resolved_test.tpl.yaml
│   ├── expected_patch.tpl.yaml
│   ├── goldens/
│   │   ├── common_labels.golden.yaml
│   │   ├── configmap.golden.yaml
│   │   ├── deployment_prefix.golden.yaml
│   │   ├── deployment_prefix_compat.golden.yaml
│   │   ├── deployment_suffix.golden.yaml
│   │   ├── deployment_suffix_compat.golden.yaml
│   │   ├── image_digest.golden.yaml
│   │   ├── image_resolved.golden.yaml
│   │   ├── name_prefix.golden.yaml
│   │   ├── name_suffix.golden.yaml
│   │   ├── namespace.golden.yaml
│   │   ├── patch.golden.yaml
│   │   ├── patch_images.golden.yaml
│   │   ├── raw.golden.yaml
│   │   └── secrets.golden.yaml
│   ├── images/
│   │   ├── BUILD.bazel
│   │   └── container_content.txt
│   ├── job.yaml
│   ├── overlay/
│   │   └── deployment.yaml
│   ├── secrets/
│   │   ├── secreta/
│   │   │   └── file1.yaml
│   │   └── secretb/
│   │       ├── file2.txt
│   │       └── foo
│   ├── service.yaml
│   ├── set_namespace_test.sh
│   ├── test.yaml
│   └── test_expected.yaml
└── tools/
    ├── BUILD.bazel
    ├── bazel
    ├── preset.bzl
    ├── preset7.bazelrc
    ├── preset8.bazelrc
    ├── preset9.bazelrc
    └── util.bzl

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

================================================
FILE: .bazelignore
================================================
examples/
.git/


================================================
FILE: .bazelrc
================================================
########################
# Import bazelrc presets
import %workspace%/tools/preset.bazelrc

# Don’t want to push a rules author to update their deps if not needed.
# https://bazel.build/reference/command-line-reference#flag--check_direct_dependencies
# https://bazelbuild.slack.com/archives/C014RARENH0/p1691158021917459?thread_ts=1691156601.420349&cid=C014RARENH0
common --config=ruleset

# 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

# Required until this is the default; expected in Bazel 7
common --enable_bzlmod

# C++17 is required by protobuf and abseil-cpp
build --cxxopt=-std=c++17
build --host_cxxopt=-std=c++17

# Ensure that the MODULE.bazel.lock file is complete and committed.
# This is an important security measure: it ensures that developers on the
# same rule set download dependencies at the same versions with the same bits.
# This setting does not affect modules that depend on this module.
#
# When updating dependencies, use --lockfile_mode=refresh, for example:
#     bazel mod tidy --lockfile_mode=refresh
#
# When testing different versions of Bazel, use --lockfile_mode=update or
# --lockfile_mode=off. The lock file format changes over time, and different
# versions of Bazel may expect different syntax. Bazel also implicitly requires
# some modules, and different versions have different dependencies, which
# also affects the contents of the lock file.
common --lockfile_mode=off

# This directory is configured in GitHub actions to be persisted between runs.
# We do not enable the repository cache to cache downloaded external artifacts
# as these are generally faster to download again than to fetch them from the
# GitHub actions cache.
common:ci --disk_cache=~/.cache/bazel

common --@protobuf//bazel/toolchains:prefer_prebuilt_protoc

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


================================================
FILE: .bcr/metadata.template.json
================================================
{
    "homepage": "https://github.com/adobe/rules_gitops",
    "maintainers": [
        {
            "name": "Nick Schaap",
            "email": "schaap@adobe.com",
            "github": "nickschaap",
            "github_user_id": 4684894
        },
        {
            "name": "Konstantin Zadorozhny",
            "email": "zadorozh@adobe.com",
            "github": "kzadorozhny",
            "github_user_id": 1616702
        }
    ],
    "repository": ["github:adobe/rules_gitops"],
    "versions": [],
    "yanked_versions": {}
}

================================================
FILE: .bcr/presubmit.yml
================================================
bcr_test_module:
    module_path: '.'
    matrix:
        bazel: ['7.x', '8.x', '9.x']
        platform: ['debian10', 'macos', 'ubuntu2004']
    tasks:
        run_tests:
            name: 'Run test module'
            bazel: ${{ bazel }}
            platform: ${{ platform }}
            test_flags:
                - "--test_tag_filters=-skip-bazel-ci"
            test_targets:
                - '//...'

================================================
FILE: .bcr/source.template.json
================================================
{
    "integrity": "**leave this alone**",
    "strip_prefix": "{REPO}-{VERSION}",
    "docs_url": "https://github.com/{OWNER}/{REPO}/releases/download/{TAG}/{REPO}-{TAG}.docs.tar.gz",
    "url": "https://github.com/{OWNER}/{REPO}/releases/download/{TAG}/{REPO}-{TAG}.tar.gz"
}

================================================
FILE: .gitattributes
================================================
#################################
# Configuration for 'git archive'
# See https://git-scm.com/docs/git-archive#ATTRIBUTES

# Don't include examples in the distribution artifact, to reduce size.
# You may want to add additional exclusions for folders or files that users don't need.
examples export-ignore
tests export-ignore

================================================
FILE: .github/CODEOWNERS
================================================
#
# The default owners for everything in the repo.
#
* @adobe/rules_gitops


================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing

Thanks for choosing to contribute!

The following are a set of guidelines to follow when contributing to this project.

## Code Of Conduct

This project adheres to the Adobe [code of conduct](../CODE_OF_CONDUCT.md). By participating,
you are expected to uphold this code. Please report unacceptable behavior to
[Grp-opensourceoffice@adobe.com](mailto:Grp-opensourceoffice@adobe.com).

## Have A Question?

Start by filing an issue. The existing committers on this project work to reach
consensus around project direction and issue solutions within issue threads
(when appropriate).

## Contributor License Agreement

All third-party contributions to this project must be accompanied by a signed contributor
license agreement. This gives Adobe permission to redistribute your contributions
as part of the project. [Sign our CLA](https://opensource.adobe.com/cla.html). You
only need to submit an Adobe CLA one time, so if you have submitted one previously,
you are good to go!

## Code Reviews

All submissions should come in the form of pull requests and need to be reviewed
by project committers. Read [GitHub's pull request documentation](https://help.github.com/articles/about-pull-requests/)
for more information on sending pull requests.

Lastly, please follow the [pull request template](PULL_REQUEST_TEMPLATE.md) when
submitting a pull request!

## From Contributor To Committer

We love contributions from our community! If you'd like to go a step beyond contributor
and become a committer with full write access and a say in the project, you must
be invited to the project. The existing committers employ an internal nomination
process that must reach lazy consensus (silence is approval) before invitations
are issued. If you feel you are qualified and want to get more deeply involved,
feel free to reach out to existing committers to have a conversation about that.

## Security Issues

Security issues shouldn't be reported on this issue tracker. Instead, [file an issue to our security experts](https://helpx.adobe.com/security/alertus.html).


================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
<!--- STOP! Before you open an issue please search this repository's issues to see if it has already been reported. This helps reduce duplicate issues from being created. -->
<!--- SECURITY DISCLOSURE: If this is a security disclosure please follow the guidelines in CONTRIBUTING.md. This helps keep folks from accidentally releasing vulnerabilities before the maintainers get a chance to fix the issue. -->

### Description of the problem / feature request:

> Replace this line with your answer.

### Feature requests: what underlying problem are you trying to solve with this feature?

> Replace this line with your answer.

### Bugs: what's the simplest, easiest way to reproduce this bug? Please provide a minimal example if possible.

> Replace this line with your answer.

### What operating system are you running Bazel on?

> Replace this line with your answer.

### What's the output of `bazel info release`?

> Replace this line with your answer.

### If `bazel info release` returns "development version" or "(@non-git)", tell us how you built Bazel.

> Replace this line with your answer.

### Any other information, logs, or outputs that you want to share?

> Replace these lines with your answer.
>
> If the files are large, upload as attachment or provide link.


================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!--- Provide a general summary of your changes in the Title above -->

## Description

<!--- Describe your changes in detail -->

## Related Issue

<!--- This project only accepts pull requests related to open issues -->
<!--- If suggesting a new feature or change, please discuss it in an issue first -->
<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->
<!--- Please link to the issue here: -->

## Motivation and Context

<!--- Why is this change required? What problem does it solve? -->

## How Has This Been Tested?

<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, and the tests you ran to -->
<!--- see how your change affects other areas of the code, etc. -->

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)

## Checklist:

<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->

- [ ] I have signed the [Adobe Open Source CLA](https://opensource.adobe.com/cla.html).
- [ ] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.
- [ ] I have read the **CONTRIBUTING** document.
- [ ] I have added tests to cover my changes.
- [ ] All new and existing tests passed.


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

on:
  push:
    branches:
      - main
      - feature/*
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-24.04
    strategy:
      matrix:
        bazel-version:
          - 7.6.0
          - 8.5.0
          - 9.0.0
    env:
      USE_BAZEL_VERSION: ${{ matrix.bazel-version }}
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Mount Bazel cache
        uses: actions/cache@v4
        with:
          path: ~/.cache/bazel
          key: bazel-${{ matrix.bazel-version }}-${{ github.sha }}
          restore-keys: |
            bazel-${{ matrix.bazel-version }}-
            bazel-
      - name: Setup build tools
        run: |
          TOOLS_DIR="${HOME}/.cache/tools"
          mkdir -p "${TOOLS_DIR}/bin/"
          curl -Ls -o "${TOOLS_DIR}/bin/bazel" "https://github.com/bazelbuild/bazelisk/releases/download/v1.14.0/bazelisk-linux-amd64"
          chmod +x "${TOOLS_DIR}/bin/bazel"
          echo "${TOOLS_DIR}/bin" >> $GITHUB_PATH
      - name: Test
        run: |
          bazel --output_base=~/.bazel/ test //... --config=ci
      - name: Build helloworld
        run: |
          cd examples/helloworld
          bazel --output_base=~/.bazel/ build //...
      - name: Build legacy docker
        if: matrix.bazel-version == '7.6.0'
        run: |
          cd examples/legacy_docker
          bazel --output_base=~/.bazel/ build //...


================================================
FILE: .github/workflows/conventional-commits.yaml
================================================
# This helps the tag.yaml action to automatically create new releases
#
# tag.yaml requires all commits to follow the conventional commit pattern so that it can
# automatically derive the next release version based on the commit history

name: Verify PR title/description
on:
  pull_request_target:
    types:
      - opened
      - edited
      - synchronize
permissions:
  pull-requests: read
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: amannn/action-semantic-pull-request@v5
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

================================================
FILE: .github/workflows/publish.yaml
================================================
# Publish new releases to Bazel Central Registry.
name: Publish to BCR
on:
  # Run the publish workflow after a successful release
  # Will be triggered from the release.yaml workflow
  workflow_call:
    inputs:
      tag_name:
        required: true
        type: string
    secrets:
      publish_token:
        required: true
  # In case of problems, let release engineers retry by manually dispatching
  # the workflow from the GitHub UI
  workflow_dispatch:
    inputs:
      tag_name:
        description: git tag being released
        required: true
        type: string
jobs:
  publish:
    uses: bazel-contrib/publish-to-bcr/.github/workflows/publish.yaml@v1.0.0
    with:
      tag_name: ${{ inputs.tag_name }}
      # GitHub repository which is a fork of the upstream where the Pull Request will be opened.
      registry_fork: adobe/bazel-central-registry
    permissions:
      attestations: write
      contents: write
      id-token: write
    secrets:
      # Necessary to push to the BCR fork, and to open a pull request against a registry
      publish_token: ${{ secrets.publish_token || secrets.BCR_PUBLISH_TOKEN }}

================================================
FILE: .github/workflows/release.yml
================================================
# Cut a release whenever a new tag is pushed to the repo.
name: Release
on:
  # Can be triggered from the tag.yaml workflow
  workflow_call:
    inputs:
      tag_name:
        required: true
        type: string
    secrets:
      publish_token:
        required: true
  # Or, developers can manually push a tag from their clone
  push:
    tags:
      - "v*.*.*"
permissions:
  id-token: write
  attestations: write
  contents: write
jobs:
  release:
    uses: bazel-contrib/.github/.github/workflows/release_ruleset.yaml@v7.2.2
    with:
      release_files: rules_gitops-*.tar.gz
      prerelease: false
      tag_name: ${{ inputs.tag_name || github.ref_name }}
  publish:
    needs: release
    uses: ./.github/workflows/publish.yaml
    with:
      tag_name: ${{ inputs.tag_name || github.ref_name }}
    secrets:
      publish_token: ${{ secrets.publish_token || secrets.BCR_PUBLISH_TOKEN }}

================================================
FILE: .github/workflows/release_prep.sh
================================================
#!/usr/bin/env bash

set -o errexit -o nounset -o pipefail

# Argument provided by reusable workflow caller, see
# https://github.com/bazel-contrib/.github/blob/d197a6427c5435ac22e56e33340dff912bc9334e/.github/workflows/release_ruleset.yaml#L72
TAG=$1
# The prefix is chosen to match what GitHub generates for source archives
# This guarantees that users can easily switch from a released artifact to a source archive
# with minimal differences in their code (e.g. strip_prefix remains the same)
PREFIX="rules_gitops-${TAG:1}"
ARCHIVE="rules_gitops-$TAG.tar.gz"

# NB: configuration for 'git archive' is in /.gitattributes
git archive --format=tar --prefix=${PREFIX}/ ${TAG} | gzip > $ARCHIVE
SHA=$(shasum -a 256 $ARCHIVE | awk '{print $1}')

# Add generated API docs to the release, see https://github.com/bazelbuild/bazel-central-registry/issues/5593
docs="$(mktemp -d)"; targets="$(mktemp)"
bazel --output_base="$docs" query --output=label --output_file="$targets" 'kind("starlark_doc_extract rule", //...)'
bazel --output_base="$docs" build --target_pattern_file="$targets"
tar --create --auto-compress \
    --directory "$(bazel --output_base="$docs" info bazel-bin)" \
    --file "$GITHUB_WORKSPACE/${ARCHIVE%.tar.gz}.docs.tar.gz" .

cat << EOF
## Using Bzlmod with Bazel 7 or greater
Add to your \`MODULE.bazel\` file:

\`\`\`starlark
bazel_dep(name = "rules_gitops", version = "${TAG:1}")
\`\`\`

EOF


================================================
FILE: .gitignore
================================================
.vscode/
.idea/
.ijwb/
.DS_Store
Thumbs.db
bazel-*
.bazelrc.user


================================================
FILE: BUILD.bazel
================================================
# Copyright 2026 Adobe. All rights reserved.
# This file is licensed to you 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 REPRESENTATIONS
# OF ANY KIND, either express or implied. See the License for the specific language
# governing permissions and limitations under the License.

# gazelle:exclude examples
# gazelle:map_kind bzl_library bzl_library @bazel_lib//:bzl_library.bzl

load("@buildifier_prebuilt//:rules.bzl", "buildifier", "buildifier_test")
load("@gazelle//:def.bzl", "DEFAULT_LANGUAGES", "gazelle", "gazelle_binary")
load("@package_metadata//licenses/rules:license.bzl", "license")
load("@package_metadata//purl:purl.bzl", "purl")
load("@package_metadata//rules:package_metadata.bzl", "package_metadata")
load("//kustomize:defs.bzl", "kustomize_binary")

buildifier(
    name = "buildifier.fix",
    exclude_patterns = ["./.git/*"],
    lint_mode = "fix",
    mode = "fix",
)

buildifier_test(
    name = "buildifier_test",
    exclude_patterns = ["./.git/*"],
    lint_mode = "warn",
    mode = "check",
    no_sandbox = True,
    workspace = ":MODULE.bazel",
)

package_metadata(
    name = "package_metadata",
    purl = purl.bazel(
        module_name(),
        module_version(),
    ),
    visibility = ["//visibility:public"],
)

license(
    name = "license",
    kind = "@package_metadata//licenses/spdx:Apache-2.0",
    text = "LICENSE",
)

gazelle_binary(
    name = "gazelle_bin",
    languages = DEFAULT_LANGUAGES + [
        "@bazel_skylib_gazelle_plugin//bzl",
    ],
)

gazelle(
    name = "gazelle",
    gazelle = "gazelle_bin",
)

kustomize_binary(
    name = "kustomize_bin",
)


================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Adobe Code of Conduct

## Our Pledge

In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, gender identity and expression, level of experience,
nationality, personal appearance, race, religion, or sexual identity and
orientation.

## Our Standards

Examples of behavior that contributes to creating a positive environment
include:

* Using welcoming and inclusive language.
* Being respectful of differing viewpoints and experiences.
* Gracefully accepting constructive criticism.
* Focusing on what is best for the community.
* Showing empathy towards other community members.

Examples of unacceptable behavior by participants include:

* The use of sexualized language or imagery and unwelcome sexual attention or
advances.
* Trolling, insulting/derogatory comments, and personal or political attacks.
* Public or private harassment.
* Publishing others' private information, such as a physical or electronic
  address, without explicit permission.
* Other conduct which could reasonably be considered inappropriate in a
  professional setting.

## Our Responsibilities

Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.

Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.

## Scope

This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at Grp-opensourceoffice@adobe.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.

Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at [https://contributor-covenant.org/version/1/4][version].

[homepage]: https://contributor-covenant.org
[version]: https://contributor-covenant.org/version/1/4/


================================================
FILE: COPYRIGHT
================================================
© Copyright 2015-2026 Adobe. All rights reserved.

Adobe holds the copyright for all the files found in this repository.

See the LICENSE file for licensing information.


================================================
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 2020 Adobe

   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.

   -----------------------------------------------------------------------

   The contents of third party dependencies in /vendor folder are covered
   by their repositories' respective licenses.

   The contents of /templating/fasttemplate folder are licensed under
   MIT License.


================================================
FILE: MODULE.bazel
================================================
"adobe/rules_gitops"

module(
    name = "rules_gitops",
    version = "",
    compatibility_level = 0,
)

bazel_dep(name = "platforms", version = "1.0.0")
bazel_dep(name = "package_metadata", version = "0.0.6")
bazel_dep(name = "protobuf", version = "33.4")
bazel_dep(name = "rules_go", version = "0.59.0")
bazel_dep(name = "rules_shell", version = "0.6.1")

go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk")
go_sdk.download(version = "1.24.12")

bazel_dep(name = "gazelle", version = "0.47.0")

go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
use_repo(go_deps, "com_github_ghodss_yaml", "com_github_google_go_cmp", "com_github_google_go_github_v32", "com_github_xanzy_go_gitlab", "io_k8s_api", "io_k8s_apimachinery", "io_k8s_client_go", "io_k8s_sigs_kind", "org_golang_google_protobuf", "org_golang_x_oauth2")

bazel_dep(name = "bazel_skylib", version = "1.8.2")
bazel_dep(name = "bazel_lib", version = "3.0.0")
bazel_dep(name = "bazelrc-preset.bzl", version = "1.6.0")

kustomize = use_extension("//kustomize:defs.bzl", "kustomize")
use_repo(kustomize, "kustomize")

register_toolchains("@kustomize//toolchains:all")

kubectl = use_extension("//kubectl:defs.bzl", "kubectl")
use_repo(kubectl, "kubectl")

register_toolchains("@kubectl//toolchains:all")

bazel_dep(name = "rules_img", version = "0.3.3")

# Dev Dependencies
bazel_dep(name = "bazel_skylib_gazelle_plugin", version = "1.8.2", dev_dependency = True)
bazel_dep(name = "buildifier_prebuilt", version = "8.2.1", dev_dependency = True)

pull = use_repo_rule("@rules_img//img:pull.bzl", "pull")

pull(
    name = "alpine",
    dev_dependency = True,
    digest = "sha256:51183f2cfa6320055da30872f211093f9ff1d3cf06f39a0bdb212314c5dc7375",
    layer_handling = "lazy",
    registries = [
        "mirror.gcr.io",
        "index.docker.io",
    ],
    repository = "library/alpine",
    tag = "3.23",
)

preset = use_repo_rule("//tools:preset.bzl", "preset")

preset(
    name = "preset.bzl",
)


================================================
FILE: README.md
================================================
# Bazel GitOps Rules

![CI](https://github.com/adobe/rules_gitops/workflows/CI/badge.svg?branch=main&event=push)

Bazel GitOps Rules provides tooling to bridge the gap between Bazel (for hermetic, reproducible, container builds) and continuous, git-operation driven, deployments. Users author standard Kubernetes manifests and kustomize overlays for their services. Bazel GitOps Rules handles image push and substitution, applies necessary kustomizations, and handles content addressed substitutions of all object references (configmaps, secrets, etc). Bazel targets are exposed for applying the rendered manifest directly to a Kubernetes cluster, or into version control facilitating deployment via Git operations.

## Features

- **Kustomize Integration**: Full [Kustomize](https://kustomize.io/) capabilities for generating and transforming manifests
- **GitOps Workflow**: Native support for Git-based deployment workflows with automatic PR creation
- **Container Image Management**: Seamless integration with [rules_img](https://github.com/bazel-contrib/rules_img) for building and pushing container images
- **Namespace Deployments**: Support for personal and team namespace deployments
- **Integration Testing**: Built-in utilities for Kubernetes integration test setup
- **Toolchain Support**: Managed kustomize and kubectl toolchains via Bzlmod

## Ruleset Overview

The ruleset is organized into the following modules:

| Module | Description |
|--------|-------------|
| `@rules_gitops//gitops:defs.bzl` | Core deployment rules (`k8s_deploy`, `gitops`) for rendering manifests and managing deployments |
| `@rules_gitops//kustomize:defs.bzl` | Kustomize rules and toolchain for manifest transformation |
| `@rules_gitops//kubectl:defs.bzl` | Kubectl toolchain for cluster interactions |
| `@rules_gitops//testing:defs.bzl` | Integration testing utilities (`k8s_test_setup`, `k8s_test_namespace`) |
| `@rules_gitops//adapters:providers.bzl` | `K8sPushInfo` provider for container image integration |
| `@rules_gitops//adapters:rules_img.bzl` | Adapter for [rules_img](https://github.com/bazel-contrib/rules_img) container images |

## Getting Started

### Installation

Add `rules_gitops` to your `MODULE.bazel`:

```starlark
bazel_dep(name = "rules_gitops", version = "1.0.0")
```

### Toolchain Setup

The ruleset provides managed toolchains for kustomize and kubectl. To use custom versions, configure the extensions in your `MODULE.bazel`:

```starlark
kustomize = use_extension("@rules_gitops//kustomize:defs.bzl", "kustomize")
kustomize.toolchain(
    version = "5.7.1",  # specify your desired version
)

kubectl = use_extension("@rules_gitops//kubectl:defs.bzl", "kubectl")
kubectl.toolchain(
    version = "1.32.2",  # specify your desired version
)
```

<a name="k8s_deploy"></a>
## k8s_deploy

The `k8s_deploy` macro creates rules that produce the `.apply` and `.gitops` targets. `k8s_deploy` takes the files listed in the `manifests`, `patches`, and `configmaps_srcs` attributes and combines (**renders**) them into one YAML file. This happens when you `bazel build` or `bazel run` a target created by the `k8s_deploy`. The file is created at `bazel-bin/path/to/package/name.yaml`. When you run a `.apply` target, it runs `kubectl apply` on this file. When you run a `.gitops` target, it copies this file to the appropriate location in the same or separate repository.

For example, let's look at the [example's k8s_deploy](./examples/helloworld/BUILD.bazel). We can peek at the file containing the rendered K8s manifests:
```bash
cd examples
bazel run //:mynamespace.show
```

### Container Image Integration

`rules_gitops` works with [rules_img](https://github.com/bazel-contrib/rules_img) for container image building and pushing. Use the `k8s_push_info` adapter to connect your images to deployments:

```starlark
load("@rules_gitops//adapters:rules_img.bzl", "k8s_push_info")
load("@rules_gitops//gitops:defs.bzl", "k8s_deploy")
load("@rules_img//img:image.bzl", "image_manifest")
load("@rules_img//img:push.bzl", "image_push")

image_manifest(
    name = "my_image",
    # ... image configuration
)

image_push(
    name = "push",
    image = ":my_image",
    registry = "my-registry.com",
    repository = "my-org/my-image",
)

k8s_push_info(
    name = "k8s_image",
    image = ":my_image",
    push = ":push",
    registry = "my-registry.com",
    repository = "my-org/my-image",
)

k8s_deploy(
    name = "my_deployment",
    cluster = "my-cluster",
    images = [":k8s_image"],
    manifests = ["deployment.yaml"],
    namespace = "{BUILD_USER}",
)
```

### Basic Usage

Once configured, you can use the generated targets:

```bash
# Show rendered manifests (useful for debugging)
bazel run //path/to:my_deployment.show

# Apply manifests to cluster using your kubectl toolchain
bazel run //path/to:my_deployment.apply

# Generate GitOps output
bazel run //path/to:my_deployment.gitops
```

See the [examples/helloworld](./examples/helloworld) directory for a complete working example.

<a name="gitops-workflow"></a>
## GitOps Workflow

The `create_gitops_prs` tool automates the creation of GitOps pull requests as part of your CI pipeline:

The simplified CI pipeline that incorporates GitOps will look like this:
```
[Checkout Code] -> [Bazel Build & Test] -> (if GitOps source branch) -> [Create GitOps PRs]
```

The *Create GitOps PRs* step usually is the last step of a CI pipeline. `rules_gitops` provides the `create_gitops_prs` command line tool that automates the process of creating pull requests.

For the full list of `create_gitops_prs` command line options, run:
```bash
bazel run @rules_gitops//gitops/prer:create_gitops_prs
```

<a name="gitops-and-deployment-supported-git-servers"></a>
### Supported Git Servers

The `--git_server` parameter defines the type of a Git server API to use. The supported Git server types are `github`, `gitlab`, and `bitbucket`.

Depending on the Git server type the `create_gitops_prs` tool will use following command line parameters:

--git_server | Parameter                            | Default
------------ | ------------------------------------ | --------------
| `github`
|            | ***--github_repo_owner***            | ``
|            | ***--github_repo***                  | ``
|            | ***--github_access_token***          | `$GITHUB_TOKEN`
|            | ***--github_enterprise_host***       | ``
| `gitlab`   |
|            | ***--gitlab_host***                  | `https://gitlab.com`
|            | ***--gitlab_repo***                  | ``
|            | ***--gitlab_access_token***          | `$GITLAB_TOKEN`
| `bitbucket`
|            | ***--bitbucket_api_pr_endpoint***    | ``
|            | ***--bitbucket_user***               | `$BITBUCKET_USER`
|            | ***--bitbucket_password***           | `$BITBUCKET_PASSWORD`

<a name="trunk-based-gitops-workflow"></a>
## Trunk Based GitOps Workflow

Let's assume the CI build pipeline described above is running the build for `https://github.com/example/repo.git`. In a trunk based branching model, all feature branches are merged into the `main` branch first. The *Create GitOps PRs* step runs on a `main` branch change. The GitOps deployments source files are located in the same repository under the `/cloud` directory.

The *Create GitOps PRs* pipeline step shell command will look like following:
```bash
GIT_ROOT_DIR=$(git rev-parse --show-toplevel)
GIT_COMMIT_ID=$(git rev-parse HEAD)
GIT_BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)
if [ "${GIT_BRANCH_NAME}" == "master"]; then
    bazel run //gitops/prer:create_gitops_prs -- \
        --workspace $GIT_ROOT_DIR \
        --git_repo https://github.com/example/repo.git \
        --git_mirror $GIT_ROOT_DIR/.git \
        --git_server github \
        --release_branch master \
        --gitops_pr_into master \
        --gitops_pr_title "This is my pull request title" \
        --gitops_pr_body "This is my pull request body message" \
        --branch_name ${GIT_BRANCH_NAME} \
        --git_commit ${GIT_COMMIT_ID} \
fi
```

The `GIT_*` variables describe the current state of the Git repository.

The `--git_repo` parameter defines the remote repository URL. In this case remote repository matches the repository of the working copy. The `--git_mirror` parameter is an optimization used to speed up the target repository clone process using reference repository (see `git clone --reference`). The `--git-server` parameter selects the type of Git server.

The `--release_branch` specifies the value of the ***release_branch_prefix*** attribute of `gitops` targets (see [k8s_deploy](#k8s_deploy)). The `--gitops_pr_into` defines the target branch for newly created pull requests. The `--branch_name` and `--git_commit` are the values used in the pull request commit message.

The `create_gitops_prs` tool will query all `gitops` targets which have set the ***deploy_branch*** attribute (see [k8s_deploy](#k8s_deploy)) and the ***release_branch_prefix*** attribute value that matches the `release_branch` parameter.

The all discovered `gitops` targets are grouped by the value of ***deploy_branch*** attribute. The one deployment branch will accumulate the output of all corresponding `gitops` targets.

For example, we define two deployments: grafana and prometheus. Both deployments share the same namespace. The deployments are grouped by namespace.
```starlark
[
    k8s_deploy(
        name = NAME,
        deploy_branch = NAMESPACE,
        ...
    )
    for NAME, CLUSTER, NAMESPACE in [
        ...
        ("stage-grafana", "stage", "monitoring-stage"),
        ("prod-grafana", "prod", "monitoring-prod"),
    ]
]
[
    k8s_deploy(
        name = NAME,
        deploy_branch = NAMESPACE,
        ...
    )
    for NAME, CLUSTER, NAMESPACE in [
        ...
        ("stage-prometheus", "stage", "monitoring-stage"),
        ("prod-prometheus", "prod", "monitoring-prod"),
    ]
]
```

As a result of the setup above the `create_gitops_prs` tool will open up to 2 potential deployment pull requests:
* from `deploy/monitoring-stage` to `main` including manifests for `stage-grafana` and `stage-prometheus`
* from `deploy/monitoring-prod` to `main` including manifests for `prod-grafana` and `prod-prometheus`

The GitOps pull request is only created (or new commits added) if the `gitops` target changes the state for the target deployment branch. The source pull request will remain open (and keep accumulation GitOps results) until the pull request is merged and source branch is deleted.

The `--stamp` parameter allows for the replacement of certain placeholders, but only when the `gitops` target changes the output's digest compared to the one already saved. The new digest of the unstamped data is also saved with the manifest. The digest is kept in a file in the same location as the YAML file, with a `.digest` extension added to its name. This is helpful when the manifests have volatile information that shouldn't be the only factor causing changes in the target deployment branch.

Here are the placeholders that can be replaced:

| Placeholder      | Replacement                                    |
|------------------|-------------------------------------------------|
| `{{GIT_REVISION}}` | Result of `git rev-parse HEAD`                  |
| `{{UTC_DATE}}`     | Result of `date -u`                             |
| `{{GIT_BRANCH}}`   | The `branch_name` argument given to `create_gitops_prs` |

`--dry_run` parameter can be used to test the tool without creating any pull requests. The tool will print the list of the potential pull requests. It is recommended to run the tool in the dry run mode as a part of the CI test suite to verify that the tool is configured correctly.

<a name="multiple-release-branches-gitops-workflow"></a>
## Multiple Release Branches GitOps Workflow

In the situation when the trunk based branching model in not suitable the `create_gitops_prs` tool supports creating GitOps pull requests before the code is merged to the `main` branch.

Both trunk and release branch workflows can coexist in the same repository.

For example, let's assume the CI build pipeline described above is running the build for `https://github.com/example/repo.git`. In a release branch branching model, features are merged into multiple target release branches. The release brach name convention is `release/team-<YYYYMMDD>`. The *Create GitOps PRs* step runs on release branch changes. GitOps deployments source files are located in the same repository `/cloud` directory in the `main` branch.

The *Create GitOps PRs* pipeline step shell command will look like following:
```bash
GIT_ROOT_DIR=$(git rev-parse --show-toplevel)
GIT_COMMIT_ID=$(git rev-parse HEAD)
GIT_BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)          # => release/team-20200101
RELEASE_BRANCH_SUFFIX=${GIT_BRANCH_NAME#"release/team"}     # => -20200101
RELEASE_BRANCH=${GIT_BRANCH_NAME%${RELEASE_BRANCH_SUFFIX}}  # => release/team
if [ "${RELEASE_BRANCH}" == "release/team"]; then
    bazel run //gitops/prer:create_gitops_prs -- \
        --workspace $GIT_ROOT_DIR \
        --git_repo https://github.com/example/repo.git \
        --git_mirror $GIT_ROOT_DIR/.git \
        --git_server github \
        --release_branch ${RELEASE_BRANCH} \
        --deployment_branch_suffix=${RELEASE_BRANCH_SUFFIX} \
        --gitops_pr_into master \
        --gitops_pr_title "This is my pull request title" \
        --gitops_pr_body "This is my pull request body message" \
        --branch_name ${GIT_BRANCH_NAME} \
        --git_commit ${GIT_COMMIT_ID} \
fi
```

The meaning of the parameters is the same as with [trunk based workflow](#trunk_based_gitops_workflow).
The `--release_branch` parameter takes the value of `release/team`. The additional parameter `--deployment_branch_suffix` will add the release branch suffix to the target deployment branch name.

If we modify the previous example:
```starlark
[
    k8s_deploy(
        name = NAME,
        deploy_branch = NAMESPACE,
        release_branch_prefix = "release/team",  # will only be selected when --release_branch=release/team
        ...
    )
    for NAME, CLUSTER, NAMESPACE in [
        ...
        ("stage-grafana", "stage", "monitoring-stage"),
        ("prod-grafana", "prod", "monitoring-prod"),
    ]
]
[
    k8s_deploy(
        name = NAME,
        deploy_branch = NAMESPACE,
        release_branch_prefix = "release/team",  # will only be selected when --release_branch=release/team
        ...
    )
    for NAME, CLUSTER, NAMESPACE in [
        ...
        ("stage-prometheus", "stage", "monitoring-stage"),
        ("prod-prometheus", "prod", "monitoring-prod"),
    ]
]
```

The result of the setup above the `create_gitops_prs` tool will open up to 2 potential deployment pull requests per release branch. Assuming release branch name is `release/team-20200101`:
* from `deploy/monitoring-stage-20200101` to `master` including manifests for `stage-grafana` and `stage-prometheus`
* from `deploy/monitoring-prod-20200101` to `master` including manifests for `prod-grafana` and `prod-prometheus`


<a name="integration-testing-support"></a>
## Integration Testing Support

**Note:** the Integration testing support has known limitations and should be considered **experimental**. The public API will not abide by semver.

Integration tests are defined in `BUILD.bazel` files like this:
```starlark
k8s_test_setup(
    name = "service_it.setup",
    kubeconfig = "@k8s_test//:kubeconfig",
    objects = [
        "//service:mynamespace",
    ],
)

java_test(
    name = "service_it",
    srcs = [
        "ServiceIT.java",
    ],
    data = [
        ":service_it.setup",
    ],
    jvm_flags = [
        "-Dk8s.setup=$(location :service_it.setup)",
    ],
    # other attributes omitted for brevity
)
```
The test is composed of two rules, a `k8s_test_setup` rule to manage the Kubernetes setup and a `java_test` rule that executes the actual test.

The `k8s_test_setup` rule produces a shell script which creates a temporary namespace (the namespace name is your username followed by five random digits) and creates a kubeconfig file that allows access to this new namespace. Inside the namespace, it creates some objects specified in the `objects` attributes. In the example, there is one target here: `//service:mynamespace`. This target represents a file containing all the Kubernetes object manifests required to run the service.

The output of the `k8s_test_setup` rule (a shell script) is referenced in the `java_test` rule. It's listed under the `data` attribute, which declares the target as a dependency, and is included in the jvm flags in this clause: `$(location :service_it.setup)`. The "location" function is specific to Bazel: given a target, it returns the path to the file produced by that target. In this case, it returns the path to the shell script created by our `k8s_test_setup` rule.

The test code launches the script to perform the test setup. The test code should also monitor the script console output to listen to the pod readiness events.

The `@k8s_test//:kubeconfig` target referenced from `k8s_test_setup` rule serves the purpose of making Kubernetes configuration available in the test sandbox. The `kubeconfig` repository rule in the `WORKSPACE` file will need, at minimum, provide the cluster name.

```starlark
load("//gitops:defs.bzl", "kubeconfig")

kubeconfig(
    name = "k8s_test",
    cluster = "dev",
)
```

## Building & Testing

### Building & Testing GitOps Rules

```bash
bazel test //...
```

### Building & Testing Examples Project

```bash
cd examples/helloworld
bazel test //...
```

## Have a Question

Find the `rules_gitops` contributors in the [#gitops](https://bazelbuild.slack.com/archives/C01SF68MTFS) channel on the [Bazel Slack](https://slack.bazel.build/).

## Contributing

Contributions are welcomed! Read the [Contributing Guide](./.github/CONTRIBUTING.md) for more information.


## Licensing

The contents of [/templating/fasttemplate](./templating/fasttemplate) are licensed under MIT License. See [LICENSE](./templating/fasttemplate/LICENSE) for more information.

All other files are licensed under the Apache V2 License. See [LICENSE](LICENSE) for more information.


================================================
FILE: WORKSPACE.bazel
================================================
# Marker that this is the root of a Bazel workspace.


================================================
FILE: adapters/BUILD.bazel
================================================
load("@bazel_lib//:bzl_library.bzl", "bzl_library")

bzl_library(
    name = "providers",
    srcs = ["providers.bzl"],
    visibility = ["//visibility:public"],
)

bzl_library(
    name = "rules_img",
    srcs = ["rules_img.bzl"],
    visibility = ["//visibility:public"],
    deps = [
        ":providers",
        "@rules_img//img:providers",
    ],
)

bzl_library(
    name = "external_image",
    srcs = ["external_image.bzl"],
    visibility = ["//visibility:public"],
    deps = [":providers"],
)


================================================
FILE: adapters/external_image.bzl
================================================
"""
Implementation of external image information provider suitable for injection into manifests
"""

load(":providers.bzl", "K8sPushInfo")

def _external_image_impl(ctx):
    sv = ctx.attr.image.split("@", 1)
    if (len(sv) == 1) and (not ctx.attr.digest):
        fail("digest must be specified either in image or as a separate attribute")
    s = sv[0].split(":", 1)[0]  #drop tag
    registry, repository = s.split("/", 1)

    #write digest to a file
    digest_file = ctx.actions.declare_file(ctx.label.name + ".digest")
    ctx.actions.write(
        output = digest_file,
        content = ctx.attr.digest,
    )
    return [
        DefaultInfo(
            files = depset([digest_file]),
        ),
        K8sPushInfo(
            image_label = ctx.label,
            legacy_image_name = ctx.attr.image_name,
            registry = registry,
            repository = repository,
            digestfile = digest_file,
        ),
    ]

external_image = rule(
    implementation = _external_image_impl,
    attrs = {
        "image": attr.string(mandatory = True, doc = "The image location, e.g. gcr.io/foo/bar:baz"),
        "image_name": attr.string(doc = "Image name, e.g. exernalserver. DEPRECATED: Use full target label instead, e.g. //images:externalserver"),
        "digest": attr.string(mandatory = True, doc = "The image digest, e.g. sha256:deadbeef"),
    },
)


================================================
FILE: adapters/providers.bzl
================================================
"""Provider definitions for container image adapters."""

K8sPushInfo = provider(
    doc = "Information required to inject image into a manifest and optionally push it",
    fields = {
        "image_label": "bazel target label of the image",
        "legacy_image_name": "Optional: short name",
        "registry": "registry where the image resides",
        "repository": "repository where the image resides",
        "digestfile": "a file containing the digest of the image",
        "pusher": "Optional: an executable target used to push the image to a remote registry",
        "run_environment": "Optional: a run environment info provider used for pushing the image",
    },
)


================================================
FILE: adapters/rules_img.bzl
================================================
"""Adapter for rules_img container images."""

load("@rules_img//img:providers.bzl", "DeployInfo", "ImageIndexInfo", "ImageManifestInfo")
load("//adapters:providers.bzl", "K8sPushInfo")

def _k8s_push_info_impl(ctx):
    digestfile = ctx.attr.image[OutputGroupInfo].digest.to_list()[0]

    return [
        DefaultInfo(),
        K8sPushInfo(
            image_label = ctx.attr.image.label,
            registry = ctx.attr.registry,
            repository = ctx.attr.repository,
            digestfile = digestfile,
            pusher = ctx.attr.push[DefaultInfo],
            run_environment = ctx.attr.push[RunEnvironmentInfo],
        ),
    ]

k8s_push_info = rule(
    implementation = _k8s_push_info_impl,
    attrs = {
        "image": attr.label(mandatory = True, providers = [[ImageManifestInfo], [ImageIndexInfo]]),
        "push": attr.label(mandatory = True, cfg = "target", providers = [DeployInfo]),
        "registry": attr.string(mandatory = True),
        "repository": attr.string(mandatory = True),
    },
)


================================================
FILE: e2e/BUILD.bazel
================================================
load("//e2e/util:util.bzl", "e2e_test", "kubectl_cmd")

kubectl_cmd(
    name = "verify_application",
    args = ["-n \"$${USER}\" wait --timeout=180s --for=condition=Available deployment.apps/helloworld"],
)

e2e_test(
    name = "manual_k8s",
    steps = [
        "//e2e/deployment:mynamespace.apply",
        "//e2e:verify_application",
        "//e2e/deployment:mynamespace.delete",
    ],
    tags = [
        "skip-bazel-ci",
    ],
)


================================================
FILE: e2e/deployment/BUILD.bazel
================================================
load("@rules_gitops//adapters:rules_img.bzl", "k8s_push_info")
load("@rules_gitops//gitops:defs.bzl", "k8s_deploy")
load("@rules_go//go:def.bzl", "go_binary", "go_library")
load("@rules_img//img:image.bzl", "image_manifest")
load("@rules_img//img:layer.bzl", "image_layer")
load("@rules_img//img:push.bzl", "image_push")

CLUSTER = "kind-kind"

USER = "kind-kind"

REGISTRY = "localhost:15000"

package(
    default_visibility = ["//e2e:__subpackages__"],
)

platform(
    name = "linux_amd64",
    constraint_values = [
        "@platforms//os:linux",
        "@platforms//cpu:x86_64",
    ],
)

go_library(
    name = "go_default_library",
    srcs = ["helloworld.go"],
    importpath = "github.com/adobe/rules_gitops/e2e/deployment",
)

go_binary(
    name = "helloworld",
    embed = [":go_default_library"],
    # Use pure Go (no CGO) to create a static binary compatible with Alpine (musl libc)
    pure = "on",
)

image_layer(
    name = "go_layer",
    srcs = {
        "/bin/app": ":helloworld",
    },
)

image_manifest(
    name = "go_image",
    base = "@alpine",
    entrypoint = ["/bin/app"],
    layers = [
        ":go_layer",
    ],
    platform = ":linux_amd64",
)

image_push(
    name = "push",
    image = ":go_image",
    registry = REGISTRY,
    repository = "rules_gitops/e2e/deployment/helloworld",
)

k8s_push_info(
    name = "k8s_image",
    image = ":go_image",
    push = ":push",
    registry = REGISTRY,
    repository = "rules_gitops/e2e/deployment/helloworld",
)

k8s_deploy(
    name = "mynamespace",
    cluster = CLUSTER,
    images = [":k8s_image"],
    manifests = [
        "manifests/deployment.yaml",
        "manifests/service.yaml",
    ],
    namespace = "{BUILD_USER}",
    user = USER,
)

k8s_deploy(
    name = "gitops",
    cluster = CLUSTER,
    deployment_branch = "e2e-deployment",
    gitops = True,
    images = [":k8s_image"],
    manifests = [
        "manifests/deployment.yaml",
        "manifests/service.yaml",
    ],
    namespace = "hwteam",
    release_branch_prefix = "main",
    user = USER,
)


================================================
FILE: e2e/deployment/helloworld.go
================================================
/*
Copyright 2020 Adobe. All rights reserved.
This file is licensed to you 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 REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/
package main

import (
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
)

var (
	port = flag.Int("port", 8080, "IP port")
)

func printenv(w http.ResponseWriter, r *http.Request) {
	for _, e := range os.Environ() {
		fmt.Fprintf(w, "%s\n", e)
	}
}

func home(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "<html><body>Hello World!</body></html>")
}

func main() {
	flag.Parse()
	http.HandleFunc("/", home)
	http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "ok\n")
	})
	http.HandleFunc("/env", printenv)
	fmt.Printf("Serving on port %d\n", *port)
	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
}


================================================
FILE: e2e/deployment/manifests/deployment.yaml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: helloworld
spec:
  replicas: 1
  selector:
    matchLabels:
      app: helloworld
  template:
    metadata:
      labels:
        app: helloworld
    spec:
      containers:
        - name: helloworld
          image: //e2e/deployment:go_image
          args:
            - --port=8080
          ports:
            - containerPort: 8080
              name: web
          resources:
            requests:
              memory: 2Mi
          readinessProbe:
            httpGet:
              path: /healthz
              port: 8080
          env:
            - name: NODE_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
            - name: POD_IP
              valueFrom:
                fieldRef:
                  fieldPath: status.podIP


================================================
FILE: e2e/deployment/manifests/service.yaml
================================================
apiVersion: v1
kind: Service
metadata:
  name: helloworld
  labels:
    app: helloworld
spec:
  ports:
    - port: 80
      name: web
      targetPort: 8080
  selector:
    app: helloworld


================================================
FILE: e2e/util/BUILD.bazel
================================================
load("@bazel_lib//:bzl_library.bzl", "bzl_library")
load("@rules_shell//shell:sh_binary.bzl", "sh_binary")

package(
    default_visibility = ["//e2e:__subpackages__"],
)

exports_files(
    [
        "test.sh",
        "kubectl_cmd.sh.tpl",
    ],
)

sh_binary(
    name = "setup",
    srcs = ["setup.sh"],
    data = [
        "//kubectl:resolved_toolchain",
        "@io_k8s_sigs_kind//:kind",
    ],
    env = {
        "KIND_BIN_PATH": "$(rlocationpath @io_k8s_sigs_kind//:kind)",
        "KUBECTL_BIN_PATH": "$(rlocationpath //kubectl:resolved_toolchain)",
    },
    toolchains = [
        "//kubectl:resolved_toolchain",
    ],
    deps = [
        "@bazel_tools//tools/bash/runfiles",
    ],
)

sh_binary(
    name = "teardown",
    srcs = ["teardown.sh"],
    data = [
        "//kubectl:resolved_toolchain",
        "@io_k8s_sigs_kind//:kind",
    ],
    env = {
        "KIND_BIN_PATH": "$(rlocationpath @io_k8s_sigs_kind//:kind)",
        "KUBECTL_BIN_PATH": "$(rlocationpath //kubectl:resolved_toolchain)",
    },
    deps = [
        "@bazel_tools//tools/bash/runfiles",
    ],
)

bzl_library(
    name = "util",
    srcs = ["util.bzl"],
    deps = [
        "@bazel_lib//lib:expand_template",
        "@rules_shell//shell:rules_bzl",
    ],
)


================================================
FILE: e2e/util/kubectl_cmd.sh.tpl
================================================
#!/usr/bin/env bash
set -o errexit

# --- begin runfiles.bash initialization v3 ---
# Copy-pasted from the Bazel Bash runfiles library v3.
set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash
# shellcheck disable=SC1090
source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \
source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \
source "$0.runfiles/$f" 2>/dev/null || \
source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
{ echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
# --- end runfiles.bash initialization v3 ---

KUBECTL_BIN=$(rlocation "${KUBECTL_BIN_PATH}")
if [[ ! -f "${KUBECTL_BIN}" ]]; then
  echo >&2 "ERROR: could not find kubectl binary"
  exit 1
fi

"${KUBECTL_BIN}" $$KUBECTL_ARGS$$

================================================
FILE: e2e/util/setup.sh
================================================
#!/usr/bin/env bash
set -o errexit

# --- begin runfiles.bash initialization v3 ---
# Copy-pasted from the Bazel Bash runfiles library v3.
set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash
# shellcheck disable=SC1090
source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \
source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \
source "$0.runfiles/$f" 2>/dev/null || \
source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
{ echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
# --- end runfiles.bash initialization v3 ---

# desired cluster name; default is "kind"
KIND_CLUSTER_NAME="${KIND_CLUSTER_NAME:-kind}"

KIND_BIN=$(rlocation "${KIND_BIN_PATH}")
if [[ ! -f "${KIND_BIN}" ]]; then
  echo >&2 "ERROR: could not find kind binary"
  exit 1
fi

KUBECTL_BIN=$(rlocation "${KUBECTL_BIN_PATH}")
if [[ ! -f "${KUBECTL_BIN}" ]]; then
  echo >&2 "ERROR: could not find kubectl binary"
  exit 1
fi

DOCKER_BIN=$(which docker)
if [[ ! -f "${DOCKER_BIN}" ]]; then
  echo >&2 "ERROR: could not find docker binary"
  exit 1
fi

echo "Using docker: ${DOCKER_BIN}"

# create registry container unless it already exists
reg_name='kind-registry'
reg_port='15000'
running="$(docker inspect -f '{{.State.Running}}' "${reg_name}" 2>/dev/null || true)"
if [ "${running}" != 'true' ]; then
  docker container rm "${reg_name}" 2>/dev/null || true
  docker run \
    -d --restart=always -e "REGISTRY_HTTP_ADDR=0.0.0.0:${reg_port}" -p "${reg_port}:${reg_port}" --name "${reg_name}" \
    registry:3
fi

# create a cluster with the local registry enabled in containerd
cat <<EOF | "${KIND_BIN}" create cluster --name "${KIND_CLUSTER_NAME}" --image "kindest/node:v1.30.13" --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
containerdConfigPatches:
- |-
  [plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:${reg_port}"]
    endpoint = ["http://${reg_name}:${reg_port}"]
EOF

# connect the registry to the cluster network
# (the network may already be connected)
docker network connect "${KIND_CLUSTER_NAME}" "${reg_name}" || true

"${KUBECTL_BIN}" config use-context kind-kind

# Document the local registry
# https://github.com/kubernetes/enhancements/tree/master/keps/sig-cluster-lifecycle/generic/1755-communicating-a-local-registry
cat <<EOF | "${KUBECTL_BIN}" apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: local-registry-hosting
  namespace: kube-public
data:
  localRegistryHosting.v1: |
    host: "localhost:${reg_port}"
    help: "https://kind.sigs.k8s.io/docs/user/local-registry/"
EOF



MYNAMESPACE=$USER

echo "Creating namespaces ${MYNAMESPACE} and hwteam"

"${KUBECTL_BIN}" create namespace $MYNAMESPACE || true
"${KUBECTL_BIN}" create namespace hwteam || true

================================================
FILE: e2e/util/teardown.sh
================================================
#!/usr/bin/env bash
set -o errexit

# --- begin runfiles.bash initialization v3 ---
# Copy-pasted from the Bazel Bash runfiles library v3.
set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash
# shellcheck disable=SC1090
source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \
source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \
source "$0.runfiles/$f" 2>/dev/null || \
source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
{ echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
# --- end runfiles.bash initialization v3 ---

DOCKER_BIN=$(which docker)
if [[ ! -f "${DOCKER_BIN}" ]]; then
  echo >&2 "ERROR: could not find docker binary"
  exit 1
fi

# desired cluster name; default is "kind"
KIND_CLUSTER_NAME="${KIND_CLUSTER_NAME:-kind}"

KIND_BIN=$(rlocation "${KIND_BIN_PATH}")
if [[ ! -f "${KIND_BIN}" ]]; then
  echo >&2 "ERROR: could not find kind binary"
  exit 1
fi

KUBECTL_BIN=$(rlocation "${KUBECTL_BIN_PATH}")
if [[ ! -f "${KUBECTL_BIN}" ]]; then
  echo >&2 "ERROR: could not find kubectl binary"
  exit 1
fi

"${KUBECTL_BIN}" config use-context kind-kind

MYNAMESPACE="${USER}"

echo "=== DEBUG: Cluster status ==="
echo "--- Nodes ---"
"${KUBECTL_BIN}" get nodes -o wide || true

echo "--- All pods in namespace ${MYNAMESPACE} ---"
"${KUBECTL_BIN}" get pods -n "${MYNAMESPACE}" -o wide || true

echo "--- Deployment status ---"
"${KUBECTL_BIN}" get deployments -n "${MYNAMESPACE}" -o wide || true

echo "--- Describe deployment helloworld ---"
"${KUBECTL_BIN}" describe deployment helloworld -n "${MYNAMESPACE}" || true

echo "--- Pod logs (if any) ---"
for pod in $("${KUBECTL_BIN}" get pods -n "${MYNAMESPACE}" -o jsonpath='{.items[*].metadata.name}' 2>/dev/null); do
  echo "=== Logs for pod: ${pod} ==="
  "${KUBECTL_BIN}" logs "${pod}" -n "${MYNAMESPACE}" --tail=50 || true
  echo "=== Events for pod: ${pod} ==="
  "${KUBECTL_BIN}" describe pod "${pod}" -n "${MYNAMESPACE}" | grep -A 20 "^Events:" || true
done

echo "--- Events in namespace ${MYNAMESPACE} ---"
"${KUBECTL_BIN}" get events -n "${MYNAMESPACE}" --sort-by='.lastTimestamp' || true

echo "=== END DEBUG ==="

"${KIND_BIN}" delete cluster -n "${KIND_CLUSTER_NAME}" || true

echo "Deleting kind-registry"

"${DOCKER_BIN}" stop kind-registry || true
"${DOCKER_BIN}" container rm kind-registry || true

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

# --- begin runfiles.bash initialization v3 ---
# Copy-pasted from the Bazel Bash runfiles library v3.
set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash
# shellcheck disable=SC1090
source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \
source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \
source "$0.runfiles/$f" 2>/dev/null || \
source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
{ echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
# --- end runfiles.bash initialization v3 ---

# Track overall exit code
exit_code=0

# Resolve teardown script path (needed for trap)
teardown_script=""
if [[ -n "${TEARDOWN:-}" ]]; then
  teardown_script=$(rlocation "${TEARDOWN}")
  if [[ -z "${teardown_script}" || ! -f "${teardown_script}" ]]; then
    echo >&2 "ERROR: could not find TEARDOWN script at rlocationpath: ${TEARDOWN}"
    exit 1
  fi
fi

# Teardown runs on exit, regardless of success or failure
cleanup() {
  if [[ -n "${teardown_script}" ]]; then
    echo "=== Running teardown: ${TEARDOWN} ==="
    "${teardown_script}" || true
    echo "=== Teardown completed ==="
  fi
}
trap cleanup EXIT

# Run setup if specified
if [[ -n "${SETUP:-}" ]]; then
  setup_script=$(rlocation "${SETUP}")
  if [[ -z "${setup_script}" || ! -f "${setup_script}" ]]; then
    echo >&2 "ERROR: could not find SETUP script at rlocationpath: ${SETUP}"
    exit 1
  fi

  echo "=== Running setup: ${SETUP} ==="
  if ! "${setup_script}"; then
    echo >&2 "=== Setup failed ==="
    exit 1
  fi
  echo "=== Setup completed ==="
fi

# Run all scripts passed as arguments
for rlocation_path in "$@"; do
  script=$(rlocation "${rlocation_path}")
  if [[ -z "${script}" || ! -f "${script}" ]]; then
    echo >&2 "ERROR: could not find script at rlocationpath: ${rlocation_path}"
    exit_code=1
    break
  fi

  echo "=== Running: ${rlocation_path} ==="
  if ! "${script}"; then
    echo >&2 "=== Failed: ${rlocation_path} ==="
    exit_code=1
    break
  fi
  echo "=== Completed: ${rlocation_path} ==="
done

if [[ ${exit_code} -eq 0 ]]; then
  echo "=== All scripts completed successfully ==="
fi

exit ${exit_code}


================================================
FILE: e2e/util/util.bzl
================================================
"""Utility macros for e2e testing with kind clusters."""

load("@bazel_lib//lib:expand_template.bzl", "expand_template")
load("@rules_shell//shell:sh_binary.bzl", "sh_binary")
load("@rules_shell//shell:sh_test.bzl", "sh_test")

def e2e_test(name, steps, **kwargs):
    """Declares an e2e test interacting with a kind cluster

    Args:
        name: name of the test target
        steps: a list of labels producing binaries to run
        **kwargs: additional options to pass to the underlying test rule
    """

    sh_test(
        name = "manual_k8s",
        srcs = ["//e2e/util:test.sh"],
        args = ["$(rlocationpath {label})".format(label = label) for label in steps],
        data = [
            "//e2e/util:setup.sh",
            "@io_k8s_sigs_kind//:kind",
            "//kubectl:resolved_toolchain",
            "//e2e/util:teardown.sh",
        ] + steps,
        env = {
            "SETUP": "$(rlocationpath //e2e/util:setup.sh)",
            "TEARDOWN": "$(rlocationpath //e2e/util:teardown.sh)",
            "KIND_BIN_PATH": "$(rlocationpath @io_k8s_sigs_kind//:kind)",
            "KUBECTL_BIN_PATH": "$(rlocationpath //kubectl:resolved_toolchain)",
        },
        deps = [
            "@bazel_tools//tools/bash/runfiles",
        ],
        **kwargs
    )

def kubectl_cmd(name, args):
    expand_template(
        name = name + ".script",
        is_executable = True,
        template = "//e2e/util:kubectl_cmd.sh.tpl",
        substitutions = {
            "$$KUBECTL_ARGS$$": " ".join(args),
        },
        out = name + ".bash",
    )

    sh_binary(
        name = name,
        srcs = [name + ".script"],
        env = {
            "KUBECTL_BIN_PATH": "$(rlocationpath //kubectl:resolved_toolchain)",
        },
        data = [
            "//kubectl:resolved_toolchain",
        ],
        deps = [
            "@bazel_tools//tools/bash/runfiles",
        ],
        toolchains = [
            "//kubectl:resolved_toolchain",
        ],
    )


================================================
FILE: examples/README.md
================================================
# Bazel GitOps Rules Examples

## Overview

Examples projects to demonstrate Bazel GitOps Rules in use:

- [helloworld](./helloworld) -- a minimal Go application with `k8s_deploy` manifests
- [legacy_docker](./legacy_docker/) -- a minimal container image built with rules_docker and adapted for use with rules_gitops





================================================
FILE: examples/helloworld/.bazelrc
================================================
common --lockfile_mode=off

================================================
FILE: examples/helloworld/.bazelversion
================================================
7.6.0


================================================
FILE: examples/helloworld/BUILD.bazel
================================================
# Copyright 2026 Adobe. All rights reserved.
# This file is licensed to you 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 REPRESENTATIONS
# OF ANY KIND, either express or implied. See the License for the specific language
# governing permissions and limitations under the License.

load("@rules_gitops//adapters:rules_img.bzl", "k8s_push_info")
load("@rules_gitops//gitops:defs.bzl", "k8s_deploy")
load("@rules_gitops//kustomize:defs.bzl", "kustomization")
load("@rules_go//go:def.bzl", "go_binary", "go_library", "go_test")
load("@rules_img//img:image.bzl", "image_index", "image_manifest")
load("@rules_img//img:layer.bzl", "image_layer")
load("@rules_img//img:load.bzl", "image_load")
load("@rules_img//img:push.bzl", "image_push")

CLUSTER = "kind-kind"

USER = "kind-kind"

REGISTRY = "localhost:15000"

platform(
    name = "linux_amd64",
    constraint_values = [
        "@platforms//os:linux",
        "@platforms//cpu:x86_64",
    ],
)

platform(
    name = "linux_arm64",
    constraint_values = [
        "@platforms//os:linux",
        "@platforms//cpu:aarch64",
    ],
)

go_library(
    name = "go_default_library",
    srcs = ["helloworld.go"],
    importpath = "github.com/adobe/rules_gitops/examples/helloworld",
    visibility = ["//visibility:private"],
)

go_test(
    name = "go_default_test",
    srcs = ["helloworld_test.go"],
    embed = [":go_default_library"],
)

go_binary(
    name = "helloworld",
    embed = [":go_default_library"],
    visibility = ["//visibility:public"],
)

image_layer(
    name = "go_layer",
    srcs = {
        "/bin/app": ":helloworld",
    },
)

image_manifest(
    name = "go_image",
    base = "@alpine",
    entrypoint = ["/bin/app"],
    layers = [
        ":go_layer",
    ],
    platform = ":linux_amd64",
)

image_index(
    name = "multiarch_image",
    manifests = [":go_image"],
    platforms = [
        ":linux_amd64",
        ":linux_arm64",
    ],
)

image_load(
    name = "load",
    image = ":go_image",
    tag = "helloworld:latest",
    visibility = ["//:__pkg__"],
)

image_push(
    name = "push",
    image = ":go_image",
    registry = REGISTRY,
    repository = "rules_gitops/examples/helloworld",
    tag = "native",
    visibility = ["//:__pkg__"],
)

image_push(
    name = "push_multiarch",
    image = ":multiarch_image",
    registry = REGISTRY,
    repository = "rules_gitops/examples/helloworld",
    tag = "native",
    visibility = ["//:__pkg__"],
)

k8s_push_info(
    name = "k8s_image",
    image = ":go_image",
    push = ":push",
    registry = REGISTRY,
    repository = "rules_gitops/examples/helloworld",
)

k8s_push_info(
    name = "k8s_image_multiarch",
    image = ":multiarch_image",
    push = ":push_multiarch",
    registry = REGISTRY,
    repository = "rules_gitops/examples/helloworld",
)

kustomization(
    name = "kustomize",
    testonly = True,
    images = [":k8s_image"],
    manifests = [
        "deployment.yaml",
    ],
    namespace = "",
)

k8s_deploy(
    name = "mynamespace",
    cluster = CLUSTER,
    images = [":k8s_image"],
    manifests = [
        "deployment.yaml",
        "service.yaml",
    ],
    namespace = "{BUILD_USER}",
    user = USER,
)

# NAMESPACE = "hwteam"

# k8s_deploy(
#     name = "canary",
#     cluster = CLUSTER,
#     deployment_branch = "helloworld-canary",
#     image_digest_tag = True,  # test optional image tagging
#     image_registry = REGISTRY,  # override the default registry for production
#     image_repository_prefix = "k8s",
#     images = {
#         "helloworld-image": ":image",
#     },
#     manifests = [
#         "deployment.yaml",
#         "service.yaml",
#     ],
#     name_suffix = "-canary",
#     namespace = NAMESPACE,
#     prefix_suffix_app_labels = True,
#     user = USER,
# )

# k8s_deploy(
#     name = "release",
#     cluster = CLUSTER,
#     deployment_branch = "helloworld-prod",
#     image_digest_tag = True,  # test optional image tagging
#     image_registry = REGISTRY,  # override the default registry host for production
#     image_repository_prefix = "k8s",
#     images = {
#         "helloworld-image": ":image",
#     },
#     manifests = [
#         "deployment.yaml",
#         "service.yaml",
#     ],
#     namespace = NAMESPACE,
#     tags = ["release"],
#     user = USER,
# )

# k8s_deploy(
#     name = "gitops_custom_path",
#     cluster = CLUSTER,
#     deployment_branch = "helloworld-gitops-custom-path",
#     gitops_path = "custom_cloud",
#     image_digest_tag = True,  # test optional image tagging
#     image_registry = REGISTRY,  # override the default registry host for production
#     image_repository_prefix = "k8s",
#     images = {
#         "helloworld-image": ":image",
#     },
#     manifests = [
#         "deployment.yaml",
#         "service.yaml",
#     ],
#     name_suffix = "-gitops-custom-path",
#     namespace = NAMESPACE,
#     user = USER,
# )

# sh_test(
#     name = "k8s_deploy_test",
#     srcs = ["k8s_deploy_test.sh"],
#     args = [
#         CLUSTER,
#         NAMESPACE,
#     ],
#     data = [
#         ":canary.show",
#         ":mynamespace.show",
#         ":release.show",
#     ],
#     deps = [
#         "@bazel_tools//tools/bash/runfiles",
#     ],
# )


================================================
FILE: examples/helloworld/MODULE.bazel
================================================
bazel_dep(name = "platforms", version = "1.0.0")

bazel_dep(name = "rules_gitops", dev_dependency = True)

kustomize = use_extension("@rules_gitops//kustomize:defs.bzl", "kustomize")
kustomize.toolchain(
    version = "5.7.1",
)

local_path_override(
    module_name = "rules_gitops",
    path = "../..",
)

bazel_dep(name = "rules_go", version = "0.52.0")

bazel_dep(name = "rules_img", version = "0.3.3", dev_dependency = True)

pull = use_repo_rule("@rules_img//img:pull.bzl", "pull")

pull(
    name = "alpine",
    digest = "sha256:51183f2cfa6320055da30872f211093f9ff1d3cf06f39a0bdb212314c5dc7375",
    layer_handling = "lazy",
    registries = [
        "mirror.gcr.io",
        "index.docker.io",
    ],
    repository = "library/alpine",
    tag = "3.23",
)


================================================
FILE: examples/helloworld/deployment.yaml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: helloworld
spec:
  replicas: 1
  selector:
    matchLabels:
      app: helloworld
  template:
    metadata:
      labels:
        app: helloworld
    spec:
      containers:
        - name: helloworld
          image: //:go_image
          args:
            - --port=8080
          ports:
            - containerPort: 8080
              name: web
          resources:
            requests:
              memory: 2Mi
          readinessProbe:
            httpGet:
              path: /healthz
              port: 8080
          env:
            - name: NODE_NAME
              valueFrom:
                fieldRef:
                  fieldPath: spec.nodeName
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
            - name: POD_IP
              valueFrom:
                fieldRef:
                  fieldPath: status.podIP


================================================
FILE: examples/helloworld/helloworld.go
================================================
/*
Copyright 2020 Adobe. All rights reserved.
This file is licensed to you 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 REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/
package main

import (
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
)

var (
	port = flag.Int("port", 8080, "IP port")
)

func printenv(w http.ResponseWriter, r *http.Request) {
	for _, e := range os.Environ() {
		fmt.Fprintf(w, "%s\n", e)
	}
}

func home(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "<html><body>Hello World!</body></html>")
}

func main() {
	flag.Parse()
	http.HandleFunc("/", home)
	http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "ok\n")
	})
	http.HandleFunc("/env", printenv)
	fmt.Printf("Serving on port %d\n", *port)
	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", *port), nil))
}


================================================
FILE: examples/helloworld/helloworld_test.go
================================================
/*
Copyright 2020 Adobe. All rights reserved.
This file is licensed to you 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 REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/
package main

import (
	"io"
	"net/http/httptest"
	"strings"
	"testing"
)

func TestHome(t *testing.T) {
	req := httptest.NewRequest("GET", "http://example.com/foo", nil)
	w := httptest.NewRecorder()
	home(w, req)

	resp := w.Result()
	if resp.StatusCode != 200 {
		t.Fatalf("Unexpected status code %d, expectted 200", resp.StatusCode)
	}
	body, _ := io.ReadAll(resp.Body)
	if !strings.Contains(string(body), "Hello World") {
		t.Error("Unexpected content returned:", string(body))
	}
}


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

# Debug
# set -x
# RUNFILES_LIB_DEBUG=1

# --- begin runfiles.bash initialization v2 ---
# Copy-pasted from the Bazel Bash runfiles library v2.
set -uo pipefail
f=bazel_tools/tools/bash/runfiles/runfiles.bash
source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null ||
  source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null ||
  source "$0.runfiles/$f" 2>/dev/null ||
  source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null ||
  source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null ||
  {
    echo >&2 "ERROR: cannot find $f"
    exit 1
  }
f=
set -e
# --- end runfiles.bash initialization v2 ---

CLUSTER="$1"
NAMESPACE="$2"

$(rlocation examples/helloworld/mynamespace.show) > mynamespace.show
echo "DEBUG: mynamespace.show:"
cat mynamespace.show

grep -F "kind: Deployment" mynamespace.show
grep -F "kind: Service" mynamespace.show
grep -F "name: helloworld" mynamespace.show
grep -E "image: localhost:15000/.*/helloworld/image@sha256" mynamespace.show
grep -E "app_label_image_short_digest" mynamespace.show | grep -v -F 'image.short-digest'

$(rlocation examples/helloworld/canary.show) > canary.show
echo "DEBUG: canary.show:"
cat canary.show

grep -F "kind: Deployment" canary.show
grep -F "kind: Service" canary.show
grep -F "namespace: $NAMESPACE" canary.show
grep -F "name: helloworld-canary" canary.show
grep -E "image: localhost:15000/k8s/helloworld/image@sha256" canary.show

$(rlocation examples/helloworld/release.show) > release.show
echo "DEBUG: release.show:"
cat release.show

grep -F "kind: Deployment" release.show
grep -F "kind: Service" release.show
grep -F "namespace: $NAMESPACE" canary.show
grep -F "name: helloworld" mynamespace.show
grep -E "image: localhost:15000/k8s/helloworld/image@sha256" release.show


================================================
FILE: examples/helloworld/service.yaml
================================================
apiVersion: v1
kind: Service
metadata:
  name: helloworld
  labels:
    app: helloworld
spec:
  ports:
    - port: 80
      name: web
      targetPort: 8080
  selector:
    app: helloworld


================================================
FILE: examples/legacy_docker/.bazelrc
================================================
common --lockfile_mode=off
common --@io_bazel_rules_docker//transitions:enable=false

================================================
FILE: examples/legacy_docker/.bazelversion
================================================
7.6.0


================================================
FILE: examples/legacy_docker/BUILD.bazel
================================================
load(
    "@io_bazel_rules_docker//container:container.bzl",
    "container_image",
    "container_push",
)
load(
    "@rules_gitops//gitops:defs.bzl",
    "k8s_deploy",
)
load(
    "//adapters:docker.bzl",
    "k8s_push_info",
)

CLUSTER = "kind-kind"

USER = "kind-kind"

REGISTRY = "localhost:15000"

platform(
    name = "linux_amd64",
    constraint_values = [
        "@platforms//os:linux",
        "@platforms//cpu:x86_64",
    ],
)

container_image(
    name = "image",
    base = "@alpine_linux_amd64//image",
    cmd = [
        "/bin/sleep",
        "10",
    ],
    files = [":container_contents.txt"],
)

genrule(
    name = "image_digestsha256",
    srcs = [":image.digest"],
    outs = ["image.digestsha256"],
    cmd = "sed 's/sha256://' $< > $@",
)

container_push(
    name = "push",
    format = "Docker",
    image = "image",
    registry = REGISTRY,
    repository = "rules_gitops/examples/legacy_docker/image",
    tag_file = "image.digestsha256",
)

k8s_push_info(
    name = "k8s_image",
    image = "image",
    push = "push",
    registry = REGISTRY,
    repository = "rules_gitops/examples/legacy_docker/image",
)

k8s_deploy(
    name = "mynamespace",
    cluster = CLUSTER,
    gitops = True,
    images = [":k8s_image"],
    manifests = [
        "deployment.yaml",
    ],
    namespace = "hwteam",
    user = USER,
)


================================================
FILE: examples/legacy_docker/MODULE.bazel
================================================
bazel_dep(name = "rules_gitops", dev_dependency = True)

kustomize = use_extension("@rules_gitops//kustomize:defs.bzl", "kustomize")
kustomize.toolchain(
    version = "5.7.1",
)

local_path_override(
    module_name = "rules_gitops",
    path = "../..",
)


================================================
FILE: examples/legacy_docker/WORKSPACE
================================================
workspace(
    name = "hzrepo",
)

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

###############################
# Golang
###############################

http_archive(
    name = "io_bazel_rules_go",
    sha256 = "0936c9bc3c4321ee372cb8f66dd972d368cb940ed01a9ba9fd7debcf0093f09b",
    urls = [
        "https://github.com/bazel-contrib/rules_go/releases/download/v0.51.0/rules_go-v0.51.0.zip",
        "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.51.0/rules_go-v0.51.0.zip",
    ],
)

load("@io_bazel_rules_go//go:deps.bzl", "go_download_sdk", "go_register_toolchains", "go_rules_dependencies")

# Download Go SDK
go_download_sdk(
    name = "go_sdk",
    version = "1.23.4",
)

go_rules_dependencies()

go_register_toolchains()

http_archive(
    name = "bazel_gazelle",
    integrity = "sha256-qAiTKSrh146u7dUNHKuY8kKhfj1XQbG5+1i1/Z0tV7w=",
    urls = [
        "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.40.0/bazel-gazelle-v0.40.0.tar.gz",
        "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.40.0/bazel-gazelle-v0.40.0.tar.gz",
    ],
)

###############################
# rules_docker
###############################

http_archive(
    name = "io_bazel_rules_docker",
    patch_args = ["-p1"],
    # https://github.com/bazelbuild/rules_docker/pull/2068#issuecomment-1242158255
    patches = ["//:patches/docker.patch"],
    sha256 = "f6dcb97e992f13bc9effd794e9bb300f06b0dadc88061f81ae68d8d5994be964",
    urls = [
        "https://mirror.bazel.build/github.com/bazelbuild/rules_docker/releases/download/v0.26.0/rules_docker-v0.26.0.tar.gz",
        "https://github.com/bazelbuild/rules_docker/releases/download/v0.26.0/rules_docker-v0.26.0.tar.gz",
    ],
)

load("@io_bazel_rules_docker//repositories:repositories.bzl", container_repositories = "repositories")

container_repositories()

load("@io_bazel_rules_docker//repositories:deps.bzl", container_deps = "deps")

container_deps()

load(
    "@io_bazel_rules_docker//container:container.bzl",
    "container_pull",
)

container_pull(
    name = "alpine_linux_amd64",
    digest = "sha256:954b378c375d852eb3c63ab88978f640b4348b01c1b3456a024a81536dafbbf4",
    registry = "index.docker.io",
    repository = "library/alpine",
    tag = "3.8",
)


================================================
FILE: examples/legacy_docker/adapters/BUILD.bazel
================================================


================================================
FILE: examples/legacy_docker/adapters/docker.bzl
================================================
"""Adapter for legacy io_bazel_rules_docker container images."""

load("@io_bazel_rules_docker//container:providers.bzl", "ImageInfo", "PushInfo")
load("@rules_gitops//adapters:providers.bzl", "K8sPushInfo")

def _k8s_push_info_impl(ctx):
    digestfile = ctx.attr.image[ImageInfo].container_parts["digest"]

    return [
        DefaultInfo(),
        K8sPushInfo(
            image_label = ctx.attr.image.label,
            registry = ctx.attr.registry,
            repository = ctx.attr.repository,
            digestfile = digestfile,
            pusher = ctx.attr.push[DefaultInfo],
        ),
    ]

k8s_push_info = rule(
    implementation = _k8s_push_info_impl,
    attrs = {
        "image": attr.label(mandatory = True, providers = [ImageInfo]),
        "push": attr.label(mandatory = True, cfg = "target", providers = [PushInfo]),
        "registry": attr.string(mandatory = True),
        "repository": attr.string(mandatory = True),
    },
)


================================================
FILE: examples/legacy_docker/container_contents.txt
================================================
Hello rules_docker!

================================================
FILE: examples/legacy_docker/deployment.yaml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: legacydocker
spec:
  replicas: 1
  selector:
    matchLabels:
      app: legacydocker
  template:
    metadata:
      labels:
        app: legacydocker
    spec:
      containers:
        - name: legacydocker
          image: //:image

================================================
FILE: examples/legacy_docker/patches/docker.patch
================================================
diff --git a/container/image.bzl b/container/image.bzl
index ceeea01..f2762d4 100644
--- a/container/image.bzl
+++ b/container/image.bzl
@@ -769,11 +769,7 @@ _outputs["build_script"] = "%{name}.executable"
 def _image_transition_impl(settings, attr):
     if not settings["@io_bazel_rules_docker//transitions:enable"]:
         # Once bazel < 5.0 is not supported we can return an empty dict here
-        return {
-            "//command_line_option:platforms": settings["//command_line_option:platforms"],
-            "@io_bazel_rules_docker//platforms:image_transition_cpu": "//plaftorms:image_transition_cpu_unset",
-            "@io_bazel_rules_docker//platforms:image_transition_os": "//plaftorms:image_transition_os_unset",
-        }
+        return {}
 
     return {
         "//command_line_option:platforms": "@io_bazel_rules_docker//platforms:image_transition",


================================================
FILE: gitops/BUILD.bazel
================================================
load("@bazel_lib//:bzl_library.bzl", "bzl_library")

# Copyright 2026 Adobe. All rights reserved.
# This file is licensed to you 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 REPRESENTATIONS
# OF ANY KIND, either express or implied. See the License for the specific language
# governing permissions and limitations under the License.

licenses(["notice"])  # Apache 2.0

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

alias(
    name = "nameprefix_deployment_labels_config.yaml",
    actual = "//gitops/private:nameprefix_deployment_labels_config.yaml",
)

alias(
    name = "namesuffix_deployment_labels_config.yaml",
    actual = "//gitops/private:namesuffix_deployment_labels_config.yaml",
)

bzl_library(
    name = "defs",
    srcs = ["defs.bzl"],
    deps = [
        "//gitops/private:gitops",
        "//gitops/private:k8s_deploy",
    ],
)


================================================
FILE: gitops/analysis/BUILD.bazel
================================================
load("@protobuf//bazel:proto_library.bzl", "proto_library")
load("@rules_go//go:def.bzl", "go_library")
load("@rules_go//proto:def.bzl", "go_proto_library")

go_library(
    name = "go_default_library",
    srcs = ["analysis.go"],
    embed = [":analysis_go_proto"],
    importpath = "github.com/adobe/rules_gitops/gitops/analysis",
    visibility = ["//visibility:public"],
)

proto_library(
    name = "analysis_proto",
    srcs = ["analysis.proto"],
    visibility = ["//visibility:public"],
    deps = ["//gitops/blaze_query:blaze_query_proto"],
)

go_proto_library(
    name = "analysis_go_proto",
    importpath = "github.com/adobe/rules_gitops/gitops/analysis",
    proto = ":analysis_proto",
    visibility = ["//visibility:public"],
    deps = ["//gitops/blaze_query:go_default_library"],
)


================================================
FILE: gitops/analysis/analysis.go
================================================
package analysis

================================================
FILE: gitops/analysis/analysis.proto
================================================
// Copyright 2018 The Bazel Authors. 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.

syntax = "proto3";

package analysis;

option java_package = "com.google.devtools.build.lib.analysis";
option java_outer_classname = "AnalysisProtos";

import "gitops/blaze_query/build.proto";

// Container for the action graph properties.
message ActionGraphContainer {
  repeated Artifact artifacts = 1;
  repeated Action actions = 2;
  repeated Target targets = 3;
  repeated DepSetOfFiles dep_set_of_files = 4;
  repeated Configuration configuration = 5;
  repeated AspectDescriptor aspect_descriptors = 6;
  repeated RuleClass rule_classes = 7;
}

// Represents a single artifact, whether it's a source file or a derived output
// file.
message Artifact {
  // Identifier for this artifact; this is an opaque string, only valid for this
  // particular dump of the analysis.
  string id = 1;

  // The relative path of the file within the execution root.
  string exec_path = 2;

  // True iff the artifact is a tree artifact, i.e. the above exec_path refers
  // a directory.
  bool is_tree_artifact = 3;
}

// Represents a single action, which is a function from Artifact(s) to
// Artifact(s).
message Action {
  // The target that was responsible for the creation of the action.
  string target_id = 1;

  // The aspects that were responsible for the creation of the action (if any).
  // In the case of aspect-on-aspect, AspectDescriptors are listed in
  // topological order of the dependency graph.
  // e.g. [A, B] would imply that aspect A is applied on top of aspect B.
  repeated string aspect_descriptor_ids = 2;

  // Encodes all significant behavior that might affect the output. The key
  // must change if the work performed by the execution of this action changes.
  // Note that the key doesn't include checksums of the input files.
  string action_key = 3;

  // The mnemonic for this kind of action.
  string mnemonic = 4;

  // The configuration under which this action is executed.
  string configuration_id = 5;

  // The command line arguments of the action. This will be only set if
  // explicitly requested.
  repeated string arguments = 6;

  // The list of environment variables to be set before executing the command.
  repeated KeyValuePair environment_variables = 7;

  // The set of input dep sets that the action depends upon. If the action does
  // input discovery, the contents of this set might change during execution.
  repeated string input_dep_set_ids = 8;

  // The list of Artifact IDs that represent the output files that this action
  // will generate.
  repeated string output_ids = 9;

  // True iff the action does input discovery during execution.
  bool discovers_inputs = 10;

  // Execution info for the action.  Remote execution services may use this
  // information to modify the execution environment, but actions will
  // generally not be aware of it.
  repeated KeyValuePair execution_info = 11;

  // The list of param files. This will be only set if explicitly requested.
  repeated ParamFile param_files = 12;
}

// Represents a single target (without configuration information) that is
// associated with an action.
message Target {
  // Identifier for this target; this is an opaque string, only valid for this
  // particular dump of the analysis.
  string id = 1;

  // Label of the target, e.g. //foo:bar.
  string label = 2;

  // Class of the rule.
  string rule_class_id = 3;
}

message RuleClass {
  // Identifier for this rule class; this is an opaque string, only valid for
  // this particular dump of the analysis.
  string id = 1;

  // Name of the rule class, e.g. cc_library.
  string name = 2;
}

// Represents an invocation specific descriptor of an aspect.
message AspectDescriptor {
  // Identifier for this aspect descriptor; this is an opaque string, only valid
  // for the particular dump of the analysis.
  string id = 1;

  // The name of the corresponding aspect. For native aspects, it's the Java
  // class name, for Skylark aspects it's the bzl file followed by a % sign
  // followed by the name of the aspect.
  string name = 2;

  // The list of parameters bound to a particular invocation of that aspect on
  // a target. Note that aspects can be executed multiple times on the same
  // target in different order.
  repeated KeyValuePair parameters = 3;
}

message DepSetOfFiles {
  // Identifier for this named set of files; this is an opaque string, only
  // valid for the particular dump of the analysis.
  string id = 1;

  // Other transitively included named set of files.
  repeated string transitive_dep_set_ids = 2;

  // The list of input artifact IDs that are immediately contained in this set.
  repeated string direct_artifact_ids = 3;
}

message Configuration {
  // Identifier for this configuration; this is an opaque string, only valid for
  // the particular dump of the analysis.
  string id = 1;

  // The mnemonic representing the build configuration.
  string mnemonic = 2;

  // The platform string.
  string platform_name = 3;

  // The checksum representation of the configuration options;
  string checksum = 4;
}

message KeyValuePair {
  // The variable name.
  string key = 1;

  // The variable value.
  string value = 2;
}

message ConfiguredTarget {
  // The target. We use blaze_query.Target defined in build.proto instead of
  // the Target defined in this file because blaze_query.Target is much heavier
  // and will output proto results similar to what users are familiar with from
  // regular blaze query.
  blaze_query.Target target = 1;

  // The configuration
  Configuration configuration = 2;
}

// Container for cquery results
message CqueryResult {
  // All the configuredtargets returns by cquery
  repeated ConfiguredTarget results = 1;
}

// Content of a param file.
message ParamFile {
  // The exec path of the param file artifact.
  string exec_path = 1;

  // The arguments in the param file.
  // Each argument corresponds to a line in the param file.
  repeated string arguments = 2;
}


================================================
FILE: gitops/bazel/BUILD.bazel
================================================
# Copyright 2026 Adobe. All rights reserved.
# This file is licensed to you 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 REPRESENTATIONS
# OF ANY KIND, either express or implied. See the License for the specific language
# governing permissions and limitations under the License.

load("@rules_go//go:def.bzl", "go_library", "go_test")

licenses(["notice"])  # Apache 2.0

go_library(
    name = "go_default_library",
    srcs = ["bazeltargets.go"],
    importpath = "github.com/adobe/rules_gitops/gitops/bazel",
    visibility = ["//visibility:public"],
)

go_test(
    name = "go_default_test",
    srcs = ["bazeltargets_test.go"],
    embed = [":go_default_library"],
)


================================================
FILE: gitops/bazel/bazeltargets.go
================================================
/*
Copyright 2020 Adobe. All rights reserved.
This file is licensed to you 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 REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/
package bazel

import "strings"

// TargetToExecutable converts bazel target name to respective executable name in bazel-bin
func TargetToExecutable(target string) string {
	if !strings.HasPrefix(target, "//") {
		return target
	}
	target = "bazel-bin/" + target[2:]
	target = strings.Replace(target, ":", "/", 1)
	return target
}


================================================
FILE: gitops/bazel/bazeltargets_test.go
================================================
/*
Copyright 2020 Adobe. All rights reserved.
This file is licensed to you 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 REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/
package bazel

import "testing"

func TestTargetToExecutableHappypath(t *testing.T) {
	s := TargetToExecutable("//rtb/bidder:rtb-uat-k8s01-iad-1b-bidder-first-uat.gitops")
	if s != "bazel-bin/rtb/bidder/rtb-uat-k8s01-iad-1b-bidder-first-uat.gitops" {
		t.Error("unexpected result", s)
	}
}


================================================
FILE: gitops/blaze_query/BUILD.bazel
================================================
load("@protobuf//bazel:proto_library.bzl", "proto_library")
load("@rules_go//proto:def.bzl", "go_proto_library")

# Copyright 2026 Adobe. All rights reserved.
# This file is licensed to you 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 REPRESENTATIONS
# OF ANY KIND, either express or implied. See the License for the specific language
# governing permissions and limitations under the License.

load("@rules_go//go:def.bzl", "go_library")

licenses(["notice"])  # Apache 2.0

go_library(
    name = "go_default_library",
    embed = [":blaze_query_go_proto"],
    importpath = "github.com/adobe/rules_gitops/gitops/blaze_query",
    visibility = ["//visibility:public"],
)

proto_library(
    name = "blaze_query_proto",
    srcs = ["build.proto"],
    visibility = ["//visibility:public"],
)

go_proto_library(
    name = "blaze_query_go_proto",
    importpath = "github.com/adobe/rules_gitops/gitops/blaze_query",
    proto = ":blaze_query_proto",
    visibility = ["//visibility:public"],
)


================================================
FILE: gitops/blaze_query/build.proto
================================================
// Copyright 2014 The Bazel Authors. 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.
//
// This file contains the protocol buffer representation of a build
// file or 'blaze query --output=proto' call.

syntax = "proto2";

package blaze_query;

// option cc_api_version = 2;
// option java_api_version = 1;

option java_package = "com.google.devtools.build.lib.query2.proto.proto2api";

message License {
  repeated string license_type = 1;
  repeated string exception = 2;
}

message StringDictEntry {
  required string key = 1;
  required string value = 2;
}

message LabelDictUnaryEntry {
  required string key = 1;
  required string value = 2;
}

message LabelListDictEntry {
  required string key = 1;
  repeated string value = 2;
}

message LabelKeyedStringDictEntry {
  required string key = 1;
  required string value = 2;
}

message StringListDictEntry {
  required string key = 1;
  repeated string value = 2;
}

// Represents an entry attribute of a Fileset rule in a build file.
message FilesetEntry {
  // Indicates what to do when a source file is actually a symlink.
  enum SymlinkBehavior {
    COPY = 1;
    DEREFERENCE = 2;
  }

  // The label pointing to the source target where files are copied from.
  required string source = 1;

  // The relative path within the fileset rule where files will be mapped.
  required string destination_directory = 2;

  // Whether the files= attribute was specified. This is necessary because
  // no files= attribute and files=[] mean different things.
  optional bool files_present = 7;

  // A list of file labels to include from the source directory.
  repeated string file = 3;

  // If this is a fileset entry representing files within the rule
  // package, this lists relative paths to files that should be excluded from
  // the set.  This cannot contain values if 'file' also has values.
  repeated string exclude = 4;

  // This field is optional because there will be some time when the new
  // PB is used by tools depending on blaze query, but the new blaze version
  // is not yet released.
  // TODO(bazel-team): Make this field required once a version of Blaze is
  // released that outputs this field.
  optional SymlinkBehavior symlink_behavior = 5 [ default=COPY ];

  // The prefix to strip from the path of the files in this FilesetEntry. Note
  // that no value and the empty string as the value mean different things here.
  optional string strip_prefix = 6;
}

// A rule attribute. Each attribute must have a type and one of the various
// value fields populated - for the most part.
//
// Attributes of BOOLEAN and TRISTATE type may set all of the int, bool, and
// string values for backwards compatibility with clients that expect them to
// be set.
//
// Attributes of INTEGER, STRING, LABEL, LICENSE, BOOLEAN, and TRISTATE type
// may set *none* of the values. This can happen if the Attribute message is
// prepared for a client that doesn't support SELECTOR_LIST, but the rule has
// a selector list value for the attribute. (Selector lists for attributes of
// other types--the collection types--are handled differently when prepared
// for such a client. The possible collection values are gathered together
// and flattened.)
//
// By checking the type, the appropriate value can be extracted - see the
// comments on each type for the associated value.  The order of lists comes
// from the blaze parsing. If an attribute is of a list type, the associated
// list should never be empty.
message Attribute {
  reserved 12, 16;

  // Indicates the type of attribute.
  enum Discriminator {
    INTEGER = 1;             // int_value
    STRING = 2;              // string_value
    LABEL = 3;               // string_value
    OUTPUT = 4;              // string_value
    STRING_LIST = 5;         // string_list_value
    LABEL_LIST = 6;          // string_list_value
    OUTPUT_LIST = 7;         // string_list_value
    DISTRIBUTION_SET = 8;    // string_list_value - order is unimportant
    LICENSE = 9;             // license
    STRING_DICT = 10;        // string_dict_value
    FILESET_ENTRY_LIST = 11; // fileset_list_value
    LABEL_LIST_DICT = 12;    // label_list_dict_value
    STRING_LIST_DICT = 13;   // string_list_dict_value
    BOOLEAN = 14;            // int, bool and string value
    TRISTATE = 15;           // tristate, int and string value
    INTEGER_LIST = 16;       // int_list_value
    UNKNOWN = 18;            // unknown type, use only for build extensions
    LABEL_DICT_UNARY = 19;   // label_dict_unary_value
    SELECTOR_LIST = 20;      // selector_list
    LABEL_KEYED_STRING_DICT = 21; // label_keyed_string_dict

    DEPRECATED_STRING_DICT_UNARY = 17;

  }

  // Values for the TriState field type.
  enum Tristate {
    NO = 0;
    YES = 1;
    AUTO = 2;
  }

  message SelectorEntry {
    reserved 12;

    // The key of the selector entry. At this time, this is the label of a
    // config_setting rule, or the pseudo-label "//conditions:default".
    optional string label = 1;

    // True if the entry's value is the default value for the type as a
    // result of the condition value being specified as None (ie:
    // {"//condition": None}).
    optional bool is_default_value = 16;

    // Exactly one of the following fields (except for glob_criteria) must be
    // populated - note that the BOOLEAN and TRISTATE caveat in Attribute's
    // comment does not apply here. The type field in the SelectorList
    // containing this entry indicates which of these fields is populated,
    // in accordance with the comments on Discriminator enum values above.
    // (To be explicit: BOOLEAN populates the boolean_value field and TRISTATE
    // populates the tristate_value field.)
    optional int32 int_value = 2;
    optional string string_value = 3;
    optional bool boolean_value = 4;
    optional Tristate tristate_value = 5;
    repeated string string_list_value = 6;
    optional License license = 7;
    repeated StringDictEntry string_dict_value = 8;
    repeated FilesetEntry fileset_list_value = 9;
    repeated LabelListDictEntry label_list_dict_value = 10;
    repeated StringListDictEntry string_list_dict_value = 11;
    repeated int32 int_list_value = 13;
    repeated LabelDictUnaryEntry label_dict_unary_value = 15;
    repeated LabelKeyedStringDictEntry label_keyed_string_dict_value = 17;

    repeated bytes DEPRECATED_string_dict_unary_value = 14;
  }

  message Selector {
    // The list of (label, value) pairs in the map that defines the selector.
    // At this time, this cannot be empty, i.e. a selector has at least one
    // entry.
    repeated SelectorEntry entries = 1;

    // Whether or not this has any default values.
    optional bool has_default_value = 2;

    // The error message when no condition matches.
    optional string no_match_error = 3;
  }

  message SelectorList {
    // The type that this selector list evaluates to, and the type that each
    // selector in the list evaluates to. At this time, this cannot be
    // SELECTOR_LIST, i.e. selector lists do not nest.
    optional Discriminator type = 1;

    // The list of selector elements in this selector list. At this time, this
    // cannot be empty, i.e. a selector list is never empty.
    repeated Selector elements = 2;
  }

  // The name of the attribute
  required string name = 1;

  // Whether the attribute was explicitly specified
  optional bool explicitly_specified = 13;

  // If this attribute has a string value or a string list value, then this
  // may be set to indicate that the value may be treated as a label that
  // isn't a dependency of this attribute's rule.
  optional bool nodep = 20;

  // The type of attribute.  This message is used for all of the different
  // attribute types so the discriminator helps for figuring out what is
  // stored in the message.
  required Discriminator type = 2;

  // If this attribute has an integer value this will be populated.
  // Boolean and TriState also use this field as [0,1] and [-1,0,1]
  // for [false, true] and [auto, no, yes] respectively.
  optional int32 int_value = 3;

  // If the attribute has a string value this will be populated.  Label and
  // path attributes use this field as the value even though the type may
  // be LABEL or something else other than STRING.
  optional string string_value = 5;

  // If the attribute has a boolean value this will be populated.
  optional bool boolean_value = 14;

  // If the attribute is a Tristate value, this will be populated.
  optional Tristate tristate_value = 15;

  // The value of the attribute has a list of string values (label and path
  // note from STRING applies here as well).
  repeated string string_list_value = 6;

  // If this is a license attribute, the license information is stored here.
  optional License license = 7;

  // If this is a string dict, each entry will be stored here.
  repeated StringDictEntry string_dict_value = 8;

  // If the attribute is part of a Fileset, the fileset entries are stored in
  // this field.
  repeated FilesetEntry fileset_list_value = 9;

  // If this is a label list dict, each entry will be stored here.
  repeated LabelListDictEntry label_list_dict_value = 10;

  // If this is a string list dict, each entry will be stored here.
  repeated StringListDictEntry string_list_dict_value = 11;

  // The value of the attribute has a list of int32 values
  repeated int32 int_list_value = 17;

  // If this is a label dict unary, each entry will be stored here.
  repeated LabelDictUnaryEntry label_dict_unary_value = 19;

  // If this is a label-keyed string dict, each entry will be stored here.
  repeated LabelKeyedStringDictEntry label_keyed_string_dict_value = 22;

  // If this attribute's value is an expression containing one or more select
  // expressions, then its type is SELECTOR_LIST and a SelectorList will be
  // stored here.
  optional SelectorList selector_list = 21;

  repeated bytes DEPRECATED_string_dict_unary_value = 18;
}

// A rule instance (e.g., cc_library foo, java_binary bar).
message Rule {
  reserved 8, 11;

  // The name of the rule (formatted as an absolute label, e.g. //foo/bar:baz).
  required string name = 1;

  // The rule class (e.g., java_library)
  required string rule_class = 2;

  // The BUILD file and line number of the location (formatted as
  // <absolute_path>:<line_number>) in the rule's package's BUILD file where the
  // rule instance was instantiated. The line number will be that of a rule
  // invocation or macro call (that in turn invoked a rule). See
  // https://docs.bazel.build/versions/master/skylark/macros.html#macro-creation
  optional string location = 3;

  // All of the attributes that describe the rule.
  repeated Attribute attribute = 4;

  // All of the inputs to the rule (formatted as absolute labels). These are
  // predecessors in the dependency graph.
  repeated string rule_input = 5;

  // All of the outputs of the rule (formatted as absolute labels). These are
  // successors in the dependency graph.
  repeated string rule_output = 6;

  // The set of all "features" inherited from the rule's package declaration.
  repeated string default_setting = 7;

  // The rule's class's public by default value.
  optional bool public_by_default = 9;

  // If this rule is of a skylark-defined RuleClass.
  optional bool is_skylark = 10;

  // Hash encapsulating the behavior of this Skylark rule. Any change to this
  // rule's definition that could change its behavior will be reflected here.
  optional string skylark_environment_hash_code = 12;
}

// Summary of all transitive dependencies of 'rule,' where each dependent
// rule is included only once in the 'dependency' field.  Gives complete
// information to analyze the single build target labeled rule.name,
// including optional location of target in BUILD file.
message RuleSummary {
  required Rule rule = 1;
  repeated Rule dependency = 2;
  optional string location = 3;
}

// A package group. Aside from the name, it contains the list of packages
// present in the group (as specified in the BUILD file).
message PackageGroup {
  reserved 4;

  // The name of the package group
  required string name = 1;

  // The list of packages as specified in the BUILD file. Currently this is
  // only a list of packages, but some time in the future, there might be
  // some type of wildcard mechanism.
  repeated string contained_package = 2;

  // The list of sub package groups included in this one.
  repeated string included_package_group = 3;
}

// An environment group.
message EnvironmentGroup {
  // The name of the environment group.
  required string name = 1;

  // The environments that belong to this group (as labels).
  repeated string environment = 2;

  // The member environments that rules implicitly support if not otherwise
  // specified.
  repeated string default = 3;
}

// A file that is an input into the build system.
// Next-Id: 10
message SourceFile {
  reserved 7;

  // The name of the source file (a label).
  required string name = 1;

  // The location of the source file.  This is a path with line numbers, not
  // a label in the build system.
  optional string location = 2;

  // Labels of .bzl (Skylark) files that are transitively loaded in this BUILD
  // file. This is present only when the SourceFile represents a BUILD file that
  // loaded .bzl files.
  // TODO(bazel-team): Rename this field.
  repeated string subinclude = 3;

  // Labels of package groups that are mentioned in the visibility declaration
  // for this source file.
  repeated string package_group = 4;

  // Labels mentioned in the visibility declaration (including :__pkg__ and
  // //visibility: ones)
  repeated string visibility_label = 5;

  // The package-level features enabled for this package. Only present if the
  // SourceFile represents a BUILD file.
  repeated string feature = 6;

  // License attribute for the file.
  optional License license = 8;

  // True if the package contains an error. Only present if the SourceFile
  // represents a BUILD file.
  optional bool package_contains_errors = 9;
}

// A file that is the output of a build rule.
message GeneratedFile {
  // The name of the generated file (a label).
  required string name = 1;

  // The label of the target that generates the file.
  required string generating_rule = 2;

  // The path of the output file (not a label).
  optional string location = 3;
}

// A target from a blaze query execution.  Similar to the Attribute message,
// the Discriminator is used to determine which field contains information.
// For any given type, only one of these can be populated in a single Target.
message Target {
  enum Discriminator {
    RULE = 1;
    SOURCE_FILE = 2;
    GENERATED_FILE = 3;
    PACKAGE_GROUP = 4;
    ENVIRONMENT_GROUP = 5;
  }

  // The type of target contained in the message.
  required Discriminator type = 1;

  // If this target represents a rule, the rule is stored here.
  optional Rule rule = 2;

  // A file that is not generated by the build system (version controlled
  // or created by the test harness).
  optional SourceFile source_file = 3;

  // A generated file that is the output of a rule.
  optional GeneratedFile generated_file = 4;

  // A package group.
  optional PackageGroup package_group = 5;

  // An environment group.
  optional EnvironmentGroup environment_group = 6;
}

// Container for all of the blaze query results.
message QueryResult {
  // All of the targets returned by the blaze query.
  repeated Target target = 1;
}

////////////////////////////////////////////////////////////////////////////
// Messages dealing with querying the BUILD language itself. For now, this is
// quite simplistic: Blaze can only tell the names of the rule classes, their
// attributes with their type.

// Information about allowed rule classes for a specific attribute of a rule.
message AllowedRuleClassInfo {
  enum AllowedRuleClasses {
    ANY = 1;  // Any rule is allowed to be in this attribute
    SPECIFIED = 2;  // Only the explicitly listed rules are allowed
  }

  required AllowedRuleClasses policy = 1;

  // Rule class names of rules allowed in this attribute, e.g "cc_library",
  // "py_binary". Only present if the allowed_rule_classes field is set to
  // SPECIFIED.
  repeated string allowed_rule_class = 2;
}

// This message represents a single attribute of a single rule.
// See docs.bazel.build/versions/master/skylark/lib/attr.html.
message AttributeDefinition {
  required string name = 1; // e.g. "name", "srcs"
  required Attribute.Discriminator type = 2;
  optional bool mandatory = 3;
  optional AllowedRuleClassInfo allowed_rule_classes = 4; // type=label*
  optional string documentation = 5;
  optional bool allow_empty = 6;       // type=*_list|*_dict
  optional bool allow_single_file = 7; // type=label
  optional AttributeValue default = 9; // simple (not computed/late-bound) values only
  optional bool executable = 10;       // type=label
  optional bool configurable = 11;
  optional bool nodep = 12;            // label-valued edge does not establish a dependency
  optional bool cfg_is_host = 13;      // edge entails a transition to "host" configuration
}

// An AttributeValue represents the value of an attribute.
// A single field, determined by the attribute type, is populated.
//
// It is used only for AttributeDefinition.default. Attribute and
// SelectorEntry do their own thing for unfortunate historical reasons.
message AttributeValue {
  optional int32 int = 1;           // type=int|tristate
  optional string string = 2;       // type=string|label|output
  optional bool bool = 3;           // type=bool
  repeated AttributeValue list = 4; // type=*_list|distrib
  repeated DictEntry dict = 5;      // type=*_dict

  message DictEntry {
    required string key = 1;
    required AttributeValue value = 2;
  }
}

message RuleDefinition {
  required string name = 1;
  // Only contains documented attributes
  repeated AttributeDefinition attribute = 2;
  optional string documentation = 3;
  // Only for build extensions: label to file that defines the extension
  optional string label = 4;
}

message BuildLanguage {
  // Only contains documented rule definitions
  repeated RuleDefinition rule = 1;
}


================================================
FILE: gitops/commitmsg/BUILD.bazel
================================================
# Copyright 2026 Adobe. All rights reserved.
# This file is licensed to you 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 REPRESENTATIONS
# OF ANY KIND, either express or implied. See the License for the specific language
# governing permissions and limitations under the License.

load("@rules_go//go:def.bzl", "go_library", "go_test")

licenses(["notice"])  # Apache 2.0

go_library(
    name = "go_default_library",
    srcs = ["commitmsg.go"],
    importpath = "github.com/adobe/rules_gitops/gitops/commitmsg",
    visibility = ["//visibility:public"],
)

go_test(
    name = "go_default_test",
    srcs = ["commitmsg_test.go"],
    deps = [":go_default_library"],
)


================================================
FILE: gitops/commitmsg/commitmsg.go
================================================
/*
Copyright 2020 Adobe. All rights reserved.
This file is licensed to you 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 REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/
package commitmsg

import (
	"log"
	"strings"
)

const begin = "--- gitops targets begin ---"
const end = "--- gitops targets end ---"

// ExtractTargets extracts list of gitops targets used in a commit
func ExtractTargets(msg string) (packages []string) {
	betweenMarkers := false
	for _, s := range strings.Split(msg, "\n") {
		switch s {
		case begin:
			betweenMarkers = true
		case end:
			betweenMarkers = false
		default:
			if betweenMarkers {
				packages = append(packages, s)
			}
		}
	}
	if betweenMarkers {
		log.Print("Unable to find end marker in commit message")
	}
	return
}

// Generate generates a commit message from a list of targets, including git branch and commit info above the targets block
func Generate(targets []string, branchName, gitCommit string) string {
	var sb strings.Builder
	sb.WriteByte('\n')
	sb.WriteString("Branch: ")
	sb.WriteString(branchName)
	sb.WriteByte('\n')
	sb.WriteString("Commit: ")
	sb.WriteString(gitCommit)
	sb.WriteByte('\n')
	sb.WriteString(begin)
	sb.WriteByte('\n')

	for _, t := range targets {
		sb.WriteString(t)
		sb.WriteByte('\n')
	}

	sb.WriteString(end)
	sb.WriteByte('\n')
	return sb.String()
}


================================================
FILE: gitops/commitmsg/commitmsg_test.go
================================================
/*
Copyright 2020 Adobe. All rights reserved.
This file is licensed to you 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 REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/
package commitmsg_test

import (
	"fmt"
	"reflect"
	"testing"

	"github.com/adobe/rules_gitops/gitops/commitmsg"
)

func TestRoundtrip(t *testing.T) {
	targets := []string{"target1", "target2"}
	msg := commitmsg.Generate(targets, "my-branch", "abc123")
	t2 := commitmsg.ExtractTargets(msg)
	if !reflect.DeepEqual(targets, t2) {
		t.Errorf("Unexpected targets after parsing: %v", t2)
	}
}

func ExampleGenerate() {
	targets := []string{"target1", "target2"}
	msg := commitmsg.Generate(targets, "my-branch", "abc123")
	fmt.Println(msg)
	// Output:
	// Branch: my-branch
	// Commit: abc123
	// --- gitops targets begin ---
	// target1
	// target2
	// --- gitops targets end ---
}


================================================
FILE: gitops/defs.bzl
================================================
# Copyright 2026 Adobe. All rights reserved.
# This file is licensed to you 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 REPRESENTATIONS
# OF ANY KIND, either express or implied. See the License for the specific language
# governing permissions and limitations under the License.

"""
GitOps rules public interface
"""

load("//gitops/private:gitops.bzl", _gitops = "gitops")
load("//gitops/private:k8s_deploy.bzl", _k8s_deploy = "k8s_deploy")

k8s_deploy = _k8s_deploy
gitops = _gitops


================================================
FILE: gitops/digester/BUILD.bazel
================================================
# Copyright 2024 Adobe. All rights reserved.
# This file is licensed to you 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 REPRESENTATIONS
# OF ANY KIND, either express or implied. See the License for the specific language
# governing permissions and limitations under the License.

load("@rules_go//go:def.bzl", "go_library")

licenses(["notice"])  # Apache 2.0

go_library(
    name = "go_default_library",
    srcs = ["digester.go"],
    importpath = "github.com/adobe/rules_gitops/gitops/digester",
    visibility = ["//visibility:public"],
)


================================================
FILE: gitops/digester/digester.go
================================================
/*
Copyright 2024 Adobe. All rights reserved.
This file is licensed to you 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 REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/
package digester

import (
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"io"
	"log"
	"os"
)

// CalculateDigest calculates the SHA256 digest of a file specified by the given path
func CalculateDigest(path string) string {
	if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {
		return ""
	}

	fi, err := os.Open(path)
	if err != nil {
		log.Fatal(err)
	}
	defer fi.Close()

	h := sha256.New()
	if _, err := io.Copy(h, fi); err != nil {
		log.Fatal(err)
	}

	return hex.EncodeToString(h.Sum(nil))
}

// GetDigest retrieves the digest of a file from a file with the same name but with a ".digest" extension
func GetDigest(path string) string {
	digestPath := path + ".digest"

	if _, err := os.Stat(digestPath); errors.Is(err, os.ErrNotExist) {
		return ""
	}

	digest, err := os.ReadFile(digestPath)
	if err != nil {
		log.Fatal(err)
	}

	return string(digest)
}

// VerifyDigest verifies the integrity of a file by comparing its calculated digest with the stored digest
func VerifyDigest(path string) bool {
	return CalculateDigest(path) == GetDigest(path)
}

// SaveDigest calculates the digest of a file at the given path and saves it to a file with the same name but with a ".digest" extension.
func SaveDigest(path string) {
	digest := CalculateDigest(path)

	digestPath := path + ".digest"

	err := os.WriteFile(digestPath, []byte(digest), 0666)
	if err != nil {
		log.Fatal(err)
	}
}


================================================
FILE: gitops/exec/BUILD
================================================
# Copyright 2026 Adobe. All rights reserved.
# This file is licensed to you 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 REPRESENTATIONS
# OF ANY KIND, either express or implied. See the License for the specific language
# governing permissions and limitations under the License.

load("@rules_go//go:def.bzl", "go_library")

licenses(["notice"])  # Apache 2.0

go_library(
    name = "go_default_library",
    srcs = ["exec.go"],
    importpath = "github.com/adobe/rules_gitops/gitops/exec",
    visibility = ["//visibility:public"],
)


================================================
FILE: gitops/exec/BUILD.bazel
================================================
# Copyright 2026 Adobe. All rights reserved.
# This file is licensed to you 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 REPRESENTATIONS
# OF ANY KIND, either express or implied. See the License for the specific language
# governing permissions and limitations under the License.

load("@rules_go//go:def.bzl", "go_library")

licenses(["notice"])  # Apache 2.0

go_library(
    name = "go_default_library",
    srcs = ["exec.go"],
    importpath = "github.com/adobe/rules_gitops/gitops/exec",
    visibility = ["//visibility:public"],
)


================================================
FILE: gitops/exec/exec.go
================================================
/*
Copyright 2020 Adobe. All rights reserved.
This file is licensed to you 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 REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/
package exec

import (
	"log"
	"os/exec"
	"strings"
)

// Ex is a shortcut for executing the command in specified dir
func Ex(dir, name string, arg ...string) (output string, err error) {
	log.Println("executing:", name, strings.Join(arg, " "))
	cmd := exec.Command(name, arg...)
	if dir != "" {
		cmd.Dir = dir
	}
	b, err := cmd.CombinedOutput()
	log.Printf("%s", string(b))
	return string(b), err
}

// Mustex executes the command name arg... in directory dir
// it will exit with fatal error if execution was not successful
func Mustex(dir, name string, arg ...string) {
	_, err := Ex(dir, name, arg...)
	if err != nil {
		log.Fatalf("ERROR: %s", err)
	}

}


================================================
FILE: gitops/git/BUILD.bazel
================================================
# Copyright 2026 Adobe. All rights reserved.
# This file is licensed to you 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 REPRESENTATIONS
# OF ANY KIND, either express or implied. See the License for the specific language
# governing permissions and limitations under the License.

load("@rules_go//go:def.bzl", "go_library")

licenses(["notice"])  # Apache 2.0

go_library(
    name = "go_default_library",
    srcs = [
        "git.go",
        "server.go",
    ],
    importpath = "github.com/adobe/rules_gitops/gitops/git",
    visibility = ["//visibility:public"],
    deps = ["//gitops/exec:go_default_library"],
)


================================================
FILE: gitops/git/bitbucket/BUILD.bazel
================================================
# Copyright 2026 Adobe. All rights reserved.
# This file is licensed to you 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 REPRESENTATIONS
# OF ANY KIND, either express or implied. See the License for the specific language
# governing permissions and limitations under the License.

load("@rules_go//go:def.bzl", "go_library", "go_test")

licenses(["notice"])  # Apache 2.0

go_library(
    name = "go_default_library",
    srcs = ["bitbucket.go"],
    importpath = "github.com/adobe/rules_gitops/gitops/git/bitbucket",
    visibility = ["//visibility:public"],
)

go_test(
    name = "go_default_test",
    srcs = ["bitbucket_test.go"],
    data = glob(["testdata/**"]),
    embed = [":go_default_library"],
)


================================================
FILE: gitops/git/bitbucket/bitbucket.go
================================================
/*
Copyright 2020 Adobe. All rights reserved.
This file is licensed to you 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 REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/
package bitbucket

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
)

var (
	apiEndpoint       = flag.String("bitbucket_api_pr_endpoint", "https://bitbucket.tubemogul.info/rest/api/1.0/projects/TM/repos/repo/pull-requests", "bitbucket pull request api endpoint with project and repo")
	bitbucketUser     = flag.String("bitbucket_user", os.Getenv("BITBUCKET_USER"), "bitbucket api user")
	bitbucketPassword = flag.String("bitbucket_password", os.Getenv("BITBUCKET_PASSWORD"), "bitbucket api user password")
)

type project struct {
	Key string `json:"key,omitempty"`
}

type repository struct {
	Slug    string  `json:"slug,omitempty"`
	Project project `json:"project"`
}

type pullrequestEndpoint struct {
	ID         string     `json:"id,omitempty"`
	Repository repository `json:"repository,omitempty"`
}

type account struct {
	User user `json:"user"`
}

type user struct {
	Name string `json:"name,omitempty"`
}

type pullrequest struct {
	Title       string               `json:"title,omitempty"`
	Description string               `json:"description,omitempty"`
	State       string               `json:"state,omitempty"`
	Open        bool                 `json:"open"`
	Closed      bool                 `json:"closed"`
	FromRef     *pullrequestEndpoint `json:"fromRef,omitempty"`
	ToRef       *pullrequestEndpoint `json:"toRef,omitempty"`
	Locked      bool                 `json:"locked"`
	Reviewers   []account            `json:"reviewers,omitempty"`
}

// CreatePR creates a pull request using branch names from and to
func CreatePR(from, to, title, body string) error {
	repo := repository{
		Slug:    "repo",
		Project: project{"TM"},
	}
	prReq := pullrequest{
		Title:       title,
		Description: body,
		State:       "OPEN",
		Open:        true,
		Closed:      false,
		FromRef: &pullrequestEndpoint{
			ID:         "refs/heads/" + from,
			Repository: repo,
		},
		ToRef: &pullrequestEndpoint{
			ID:         "refs/heads/" + to,
			Repository: repo,
		},
		Locked:    false,
		Reviewers: []account{},
	}
	json, err := json.Marshal(&prReq)
	if err != nil {
		return fmt.Errorf("Unable to marshal CreatePR request: %w", err)
	}
	req, err := http.NewRequest("POST", *apiEndpoint, bytes.NewBuffer(json))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json; charset=utf-8")
	req.SetBasicAuth(*bitbucketUser, *bitbucketPassword)
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return fmt.Errorf("Unable to send CreatePR request: %w", err)
	}
	log.Printf("bitbucket api response: %s", resp.Status)
	defer resp.Body.Close()
	responseBody, err := ioutil.ReadAll(resp.Body)
	log.Print("bitbucket response: ", string(responseBody))
	// 201 created
	// 409 already exists
	if resp.StatusCode == 201 {
		log.Print("PR was created")
		return nil
	}
	if resp.StatusCode == 409 {
		log.Print("reusing existing PR")
		return nil
	}
	return fmt.Errorf("Unrecognized bitbucket response %d", resp.StatusCode)
}


================================================
FILE: gitops/git/bitbucket/bitbucket_test.go
================================================
/*
Copyright 2020 Adobe. All rights reserved.
This file is licensed to you 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 REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/
package bitbucket

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestCreatePRRemote(t *testing.T) {
	t.Skip("Manual")
	user := "********"
	pass := "*************"
	bitbucketUser = &user
	bitbucketPassword = &pass
	err := CreatePR("deploy/test1", "feature/AP-0000", "test", "hello world")
	if err != nil {
		t.Error("Unexpected error from server: ", err)
	}
}

func TestCreatePRNew(t *testing.T) {
	var buf []byte
	var srverr error
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		buf, srverr = ioutil.ReadAll(r.Body)
		http.Error(w, "Created", 201)
		fmt.Fprintln(w, "PR created")
	}))
	defer ts.Close()
	oldendpoint := *apiEndpoint
	defer func() { *apiEndpoint = oldendpoint }()
	*apiEndpoint = ts.URL
	err := CreatePR("deploy/test1", "feature/AP-0000", "test", "hello world")
	if err != nil {
		t.Error("Unexpected error from server: ", err)
	}
	if srverr != nil {
		t.Error("Unexpected error: ", srverr)
	}
	expectedreq := `{"title":"test","description":"hello world","state":"OPEN","open":true,"closed":false,"fromRef":{"id":"refs/heads/deploy/test1","repository":{"slug":"repo","project":{"key":"TM"}}},"toRef":{"id":"refs/heads/feature/AP-0000","repository":{"slug":"repo","project":{"key":"TM"}}},"locked":false}`
	if string(buf) != expectedreq {
		t.Error("Unexpected request body: ", string(buf))
	}
}


================================================
FILE: gitops/git/bitbucket/testdata/create_pr.json
================================================
{
    "title": "Deploy to test 1",
    "description": "Deploy to test 1.",
    "state": "OPEN",
    "open": true,
    "closed": false,
    "fromRef": {
        "id": "refs/heads/deploy/test_1",
        "repository": {
            "slug": "repo",
            "name": null,
            "project": {
                "key": "TM"
            }
        }
    },
    "toRef": {
        "id": "refs/heads/master",
        "repository": {
            "slug": "repo",
            "name": null,
            "project": {
                "key": "TM"
            }
        }
    },
    "locked": false,
    "reviewers": [
        {
            "user": {
                "name": "aleksey.pesternikov"
            }
        }
    ]
}


================================================
FILE: gitops/git/git.go
================================================
/*
Copyright 2020 Adobe. All rights reserved.
This file is licensed to you 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 REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/
package git

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	oe "os/exec"
	"path/filepath"
	"strings"

	"github.com/adobe/rules_gitops/gitops/exec"
)

var (
	git = "git"
)

// Clone clones a repository. Pass the full repository name, such as
// "https://aleksey.pesternikov@bitbucket.tubemogul.info/scm/tm/repo.git" as the repo.
// Cloned directory will be clean of local changes with primaryBranch branch checked out.
// repo: https://aleksey.pesternikov@bitbucket.tubemogul.info/scm/tm/repo.git
// dir: /tmp/cloudrepo
// mirrorDir: optional (if not empty) local mirror of the repository
func Clone(repo, dir, mirrorDir, primaryBranch, gitopsPath string) (*Repo, error) {
	if err := os.RemoveAll(dir); err != nil {
		return nil, fmt.Errorf("Unable to clone repo: %w", err)
	}
	remoteName := "origin"
	args := []string{"clone", "--no-checkout", "--single-branch", "--branch", primaryBranch, "--filter=blob:none", "--no-tags", "--origin", remoteName}
	if mirrorDir != "" {
		args = append(args, "--reference", mirrorDir)
	}
	args = append(args, repo, dir)
	exec.Mustex("", "git", args...)
	// Enable sparse-checkout when restricting to a subdir
	if !isRootPath(gitopsPath) {
		exec.Mustex(dir, "git", "config", "--local", "core.sparsecheckout", "true")
		genPath := fmt.Sprintf("%s/\n", gitopsPath)
		if err := ioutil.WriteFile(filepath.Join(dir, ".git/info/sparse-checkout"), []byte(genPath), 0644); err != nil {
			return nil, fmt.Errorf("Unable to create .git/info/sparse-checkout: %w", err)
		}
	}
	exec.Mustex(dir, "git", "checkout", primaryBranch)
	return &Repo{
		Dir:        dir,
		RemoteName: remoteName,
	}, nil
}

// Repo is a clone of a git repository. Create with Clone, and don't
// forget to clean it up after.
type Repo struct {
	// Dir is the location of the git repo.
	Dir string
	// RemoteName is the name of the remote that tracks upstream repository.
	RemoteName string
}

// Clean cleans up the repo
func (r *Repo) Clean() error {
	return os.RemoveAll(r.Dir)
}

// Fetch branches from the remote repository based on a specified pattern.
// The branches will be be added to the list tracked remote branches ready to be pushed.
func (r *Repo) Fetch(pattern string) {
	exec.Mustex(r.Dir, "git", "remote", "set-branches", "--add", r.RemoteName, pattern)
	exec.Mustex(r.Dir, "git", "fetch", "--force", "--filter=blob:none", "--no-tags", r.RemoteName)
}

// SwitchToBranch switch the repo to specified branch and checkout primaryBranch files over it.
// if branch does not exist it will be created
func (r *Repo) SwitchToBranch(branch, primaryBranch string) (new bool) {
	if _, err := exec.Ex(r.Dir, "git", "checkout", branch); err != nil {
		// error checking out, create new
		exec.Mustex(r.Dir, "git", "branch", branch, primaryBranch)
		exec.Mustex(r.Dir, "git", "checkout", branch)
		return true
	}
	return false
}

// RecreateBranch discards a branch content and reset it from primaryBranch.
func (r *Repo) RecreateBranch(branch, primaryBranch string) {
	exec.Mustex(r.Dir, "git", "checkout", primaryBranch)
	exec.Mustex(r.Dir, "git", "branch", "-f", branch, primaryBranch)
	exec.Mustex(r.Dir, "git", "checkout", branch)
}

// GetLastCommitMessage fetches the commit message from the most recent change of the branch
func (r *Repo) GetLastCommitMessage() (msg string) {
	msg, err := exec.Ex(r.Dir, "git", "log", "-1", "--pretty=%B")
	if err != nil {
		return ""
	}
	return msg
}

// Commit all changes to the current branch. returns true if there were any changes
func (r *Repo) Commit(message, gitopsPath string) bool {
	if isRootPath(gitopsPath) {
		exec.Mustex(r.Dir, "git", "add", ".")
	} else {
		exec.Mustex(r.Dir, "git", "add", gitopsPath)
	}
	if r.IsClean() {
		return false
	}
	exec.Mustex(r.Dir, "git", "commit", "-a", "-m", message)
	return true
}

// RestoreFile restores the specified file in the repository to its original state
func (r *Repo) RestoreFile(fileName string) {
	exec.Mustex(r.Dir, "git", "checkout", "--", fileName)
}

// GetChangedFiles returns a list of files that have been changed in the repository
func (r *Repo) GetChangedFiles() []string {
	s, err := exec.Ex(r.Dir, "git", "diff", "--name-only")
	if err != nil {
		log.Fatalf("ERROR: %s", err)
	}
	var files []string
	sc := bufio.NewScanner(strings.NewReader(s))
	for sc.Scan() {
		files = append(files, sc.Text())
	}
	if err := sc.Err(); err != nil {
		log.Fatalf("ERROR: %s", err)
	}
	return files
}

// IsClean returns true if there is no local changes (nothing to commit)
func (r *Repo) IsClean() bool {
	cmd := oe.Command("git", "status", "--porcelain")
	cmd.Dir = r.Dir
	b, err := cmd.CombinedOutput()
	if err != nil {
		log.Fatalf("ERROR: %s", err)
	}
	return len(b) == 0
}

// Push pushes all local changes to the remote repository
// all changes should be already commited
func (r *Repo) Push(branches []string) {
	args := append([]string{"push", r.RemoteName, "-f", "--set-upstream"}, branches...)
	exec.Mustex(r.Dir, "git", args...)
}

// isRootPath is an internal helper to detect "full repo" case.
func isRootPath(gitopsPath string) bool {
	return gitopsPath == "" || gitopsPath == "."
}

================================================
FILE: gitops/git/github/BUILD.bazel
================================================
load("@rules_go//go:def.bzl", "go_library")

go_library(
    name = "go_default_library",
    srcs = ["github.go"],
    importpath = "github.com/adobe/rules_gitops/gitops/git/github",
    visibility = ["//visibility:public"],
    deps = [
        "@com_github_google_go_github_v32//github:go_default_library",
        "@org_golang_x_oauth2//:go_default_library",
    ],
)


================================================
FILE: gitops/git/github/github.go
================================================
package github

import (
	"context"
	"errors"
	"flag"
	"io/ioutil"
	"log"
	"net/http"
	"os"

	"github.com/google/go-github/v32/github"
	"golang.org/x/oauth2"
)

var (
	repoOwner            = flag.String("github_repo_owner", "", "the owner user/organization to use for github api requests")
	repo                 = flag.String("github_repo", "", "the repo to use for github api requests")
	pat                  = flag.String("github_access_token", os.Getenv("GITHUB_TOKEN"), "the access token to authenticate requests")
	githubEnterpriseHost = flag.String("github_enterprise_host", "", "The host name of the private enterprise github, e.g. git.corp.adobe.com")
)

func CreatePR(from, to, title, body string) error {
	if *repoOwner == "" {
		return errors.New("github_repo_owner must be set")
	}
	if *repo == "" {
		return errors.New("github_repo must be set")
	}
	if *pat == "" {
		return errors.New("github_access_token must be set")
	}

	ctx := context.Background()
	ts := oauth2.StaticTokenSource(
		&oauth2.Token{AccessToken: *pat},
	)
	tc := oauth2.NewClient(ctx, ts)

	var gh *github.Client
	if *githubEnterpriseHost != "" {
		baseUrl := "https://" + *githubEnterpriseHost + "/api/v3/"
		uploadUrl := "https://" + *githubEnterpriseHost + "/api/uploads/"
		var err error
		gh, err = github.NewEnterpriseClient(baseUrl, uploadUrl, tc)
		if err != nil {
			log.Println("Error in creating github client", err)
			return nil
		}
	} else {
		gh = github.NewClient(tc)
	}

	pr := &github.NewPullRequest{
		Title:               &title,
		Head:                &from,
		Base:                &to,
		Body:                &body,
		Issue:               nil,
		MaintainerCanModify: new(bool),
		Draft:               new(bool),
	}
	createdPr, resp, err := gh.PullRequests.Create(ctx, *repoOwner, *repo, pr)
	if err == nil {
		log.Println("Created PR: ", *createdPr.URL)
		return err
	}

	if resp.StatusCode == http.StatusUnprocessableEntity {
		// Handle the case: "Create PR" request fails because it already exists
		log.Println("Reusing existing PR")
		return nil
	}

	// All other github responses
	defer resp.Body.Close()
	responseBody, readingErr := ioutil.ReadAll(resp.Body)
	if readingErr != nil {
		log.Println("cannot read response body")
	} else {
		log.Println("github response: ", string(responseBody))
	}

	return err
}


================================================
FILE: gitops/git/gitlab/BUILD.bazel
================================================
load("@rules_go//go:def.bzl", "go_library", "go_test")

go_library(
    name = "go_default_library",
    srcs = ["gitlab.go"],
    importpath = "github.com/adobe/rules_gitops/gitops/git/gitlab",
    visibility = ["//visibility:public"],
    deps = ["@com_github_xanzy_go_gitlab//:go_default_library"],
)

go_test(
    name = "go_default_test",
    srcs = ["gitlab_test.go"],
    embed = [":go_default_library"],
)


================================================
FILE: gitops/git/gitlab/gitlab.go
================================================
package gitlab

import (
	"errors"
	"flag"
	"io/ioutil"
	"log"
	"net/http"
	"os"

	"github.com/xanzy/go-gitlab"
)

var (
	gitlabHost  = flag.String("gitlab_host", "https://gitlab.com", "The host name of the gitlab instance")
	repo        = flag.String("gitlab_repo", "", "the repo to use for gitlab api requests")
	accessToken = flag.String("gitlab_access_token", os.Getenv("GITLAB_TOKEN"), "the access token to authenticate requests")
)

func CreatePR(from, to, title, body string) error {
	if *accessToken == "" {
		return errors.New("gitlab_access_token must be set")
	}

	opts := gitlab.CreateMergeRequestOptions{
		Title:              &title,
		Description:        nil,
		SourceBranch:       &from,
		TargetBranch:       &to,
		Labels:             nil,
		AssigneeID:         nil,
		AssigneeIDs:        nil,
		ReviewerIDs:        nil,
		TargetProjectID:    nil,
		MilestoneID:        nil,
		RemoveSourceBranch: nil,
		Squash:             nil,
		AllowCollaboration: nil,
	}

	gl, err := gitlab.NewClient(*accessToken, gitlab.WithBaseURL(*gitlabHost))
	if err != nil {
		return err
	}

	createdPr, resp, err := gl.MergeRequests.CreateMergeRequest(*repo, &opts)
	if err == nil {
		log.Println("Created MR: ", createdPr.WebURL)
		return nil
	}

	if resp.StatusCode == http.StatusConflict {
		// Handle the case: "Create MR" request fails because it already exists for this source branch
		log.Println("Reusing existing MR")
		return nil
	}

	// All other gitlab responses
	defer resp.Body.Close()
	responseBody, readingErr := ioutil.ReadAll(resp.Body)
	if readingErr != nil {
		log.Println("cannot read response body")
	} else {
		log.Println("gitlab response: ", string(responseBody))
	}

	return err
}


================================================
FILE: gitops/git/gitlab/gitlab_test.go
================================================
package gitlab

import "testing"

func TestCreatePRRemote(t *testing.T) {
	t.Skip("Manual")
	var (
		testGitlabToken = "********"
	)
	accessToken = &testGitlabToken
	type args struct {
		from  string
		to    string
		title string
		body  string
	}
	tests := []struct {
		repo    string
		args    args
		wantErr bool
	}{
		{
			repo: "cotocisternas/rules_gitops_gitlab_test",
			args: args{
				from:  "feature/gitlab-test",
				to:    "master",
				title: "test_gitlab",
			},
			wantErr: false,
		},
		{
			repo: "cotocisternas/rules_gitops_gitlab_test",
			args: args{
				from:  "feature/gitlab-test",
				to:    "master",
				title: "test_gitlab",
				body:  "hello world",
			},
			wantErr: false,
		},
		{
			repo: "petabytecl/subgroup_rules_gitops_gitlab_test/rules_gitops_gitlab_test",
			args: args{
				from:  "feature/gitlab-test",
				to:    "master",
				title: "test_gitlab",
				body:  "hello world",
			},
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.repo, func(t *testing.T) {
			repo = &tt.repo
			if err := CreatePR(tt.args.from, tt.args.to, tt.args.title, tt.args.body); (err != nil) != tt.wantErr {
				t.Errorf("CreatePR() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}


================================================
FILE: gitops/git/server.go
================================================
package git

type Server interface {
	CreatePR(from, to, title, body string) error
}

type ServerFunc func(from, to, title, body string) error

func (f ServerFunc) CreatePR(from, to, title, body string) error {
	if body == "" {
		body = title
	}

	return f(from, to, title, body)
}


================================================
FILE: gitops/prer/BUILD.bazel
================================================
# Copyright 2026 Adobe. All rights reserved.
# This file is licensed to you 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 REPRESENTATIONS
# OF ANY KIND, either express or implied. See the License for the specific language
# governing permissions and limitations under the License.

load("@rules_go//go:def.bzl", "go_binary", "go_library")

licenses(["notice"])  # Apache 2.0

go_library(
    name = "go_default_library",
    srcs = ["create_gitops_prs.go"],
    importpath = "github.com/adobe/rules_gitops/gitops/prer",
    visibility = ["//visibility:private"],
    deps = [
        "//gitops/analysis:go_default_library",
        "//gitops/bazel:go_default_library",
        "//gitops/commitmsg:go_default_library",
        "//gitops/digester:go_default_library",
        "//gitops/exec:go_default_library",
        "//gitops/git:go_default_library",
        "//gitops/git/bitbucket:go_default_library",
        "//gitops/git/github:go_default_library",
        "//gitops/git/gitlab:go_default_library",
        "//templating/fasttemplate:go_default_library",
        "@org_golang_google_protobuf//proto:go_default_library",
    ],
)

go_binary(
    name = "create_gitops_prs",
    embed = [":go_default_library"],
    visibility = ["//visibility:public"],
)


================================================
FILE: gitops/prer/create_gitops_prs.go
================================================
/*
Copyright 2020 Adobe. All rights reserved.
This file is licensed to you 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 REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/
package main

import (
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	oe "os/exec"
	"strings"

	"github.com/adobe/rules_gitops/gitops/analysis"
	"github.com/adobe/rules_gitops/gitops/bazel"
	"github.com/adobe/rules_gitops/gitops/commitmsg"
	"github.com/adobe/rules_gitops/gitops/digester"
	"github.com/adobe/rules_gitops/gitops/exec"
	"github.com/adobe/rules_gitops/gitops/git"
	"github.com/adobe/rules_gitops/gitops/git/bitbucket"
	"github.com/adobe/rules_gitops/gitops/git/github"
	"github.com/adobe/rules_gitops/gitops/git/gitlab"
	"github.com/adobe/rules_gitops/templating/fasttemplate"

	"google.golang.org/protobuf/proto"
)

func init() {
	log.SetFlags(log.LstdFlags | log.Lshortfile)
}

var (
	releaseBranch          = flag.String("release_branch", "main", "filter gitops targets by release branch")
	bazelCmd               = flag.String("bazel_cmd", "tools/bazel", "bazel binary to use")
	workspace              = flag.String("workspace", "", "path to workspace root")
	repo                   = flag.String("git_repo", "", "git repo location")
	gitMirror              = flag.String("git_mirror", "", "git mirror location, like /mnt/mirror/bitbucket.tubemogul.info/tm/repo.git for jenkins")
	gitopsPath             = flag.String("gitops_path", "cloud", "location to store files in repo.")
	gitopsTmpDir           = flag.String("gitops_tmpdir", os.TempDir(), "location to check out git tree with /cloud.")
	target                 = flag.String("target", "//... except //experimental/...", "target to scan. Useful for debugging only")
	prInto                 = flag.String("gitops_pr_into", "main", "use this branch as the source branch and target for deployment PR")
	prBody                 = flag.String("gitops_pr_body", "", "a body message for deployment PR")
	prTitle                = flag.String("gitops_pr_title", "", "a title for deployment PR")
	branchName             = flag.String("branch_name", "unknown", "Branch name to use in commit message")
	gitCommit              = flag.String("git_commit", "unknown", "Git commit to use in commit message")
	deploymentBranchPrefix = flag.String("deployment_branch_prefix", "deploy/", "the prefix to add to all deployment branch names")
	deploymentBranchSuffix = flag.String("deployment_branch_suffix", "", "suffix to add to all deployment branch names")
	gitHost                = flag.String("git_server", "bitbucket", "the git server api to use. 'bitbucket', 'github' or 'gitlab'")
	stamp                  = flag.Bool("stamp", false, "Stamp results of gitops targets with volatile information")
	dryRun                 = flag.Bool("dry_run", false, "Do not create PRs, just print what would be done")
)

func bazelQuery(query string) *analysis.CqueryResult {
	log.Println("Executing bazel cquery ", query)
	cmd := oe.Command(*bazelCmd, "cquery", query, "--output=proto")
	stderr, err := cmd.StderrPipe()
	if err != nil {
		log.Fatal(err)
	}
	go func() {
		io.Copy(os.Stderr, stderr)
	}()
	buildproto, err := cmd.Output()
	if err != nil {
		log.Fatal(err)
	}
	qr := &analysis.CqueryResult{}
	if err := proto.Unmarshal(buildproto, qr); err != nil {
		log.Fatal(err)
	}
	return qr
}

func getGitStatusDict(workdir *git.Repo, gitCommit, branchName string) map[string]interface{} {
	utcDate, err := exec.Ex("", "date", "-u")
	if err != nil {
		log.Fatal(err)
	}
	utcDate = strings.TrimSpace(utcDate)

	ctx := map[string]interface{}{
		"GIT_REVISION": gitCommit,
		"UTC_DATE":     utcDate,
		"GIT_BRANCH":   branchName,
	}

	return ctx
}

func stampFile(fullPath string, ctx map[string]interface{}) {
	template, err := os.ReadFile(fullPath)
	if err != nil {
		log.Fatal(err)
	}

	outf, err := os.OpenFile(fullPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
	if err != nil {
		log.Fatal(err)
	}
	defer outf.Close()

	_, err = fasttemplate.Execute(string(template), "{{", "}}", outf, ctx)
	if err != nil {
		log.Fatal(err)
	}
}

func main() {
	flag.Parse()
	if *workspace != "" {
		if err := os.Chdir(*workspace); err != nil {
			log.Fatal(err)
		}
	}

	var gitServer git.Server
	switch *gitHost {
	case "github":
		gitServer = git.ServerFunc(github.CreatePR)
	case "gitlab":
		gitServer = git.ServerFunc(gitlab.CreatePR)
	case "bitbucket":
		gitServer = git.ServerFunc(bitbucket.CreatePR)
	default:
		log.Fatalf("unknown vcs host: %s", *gitHost)
	}

	q := fmt.Sprintf("attr(deployment_branch, \".+\", attr(release_branch_prefix, \"%s\", kind(gitops, %s)))", *releaseBranch, *target)
	qr := bazelQuery(q)
	releaseTrains := make(map[string][]string)
	for _, t := range qr.Results {
		var releaseTrain string
		for _, a := range t.Target.GetRule().GetAttribute() {
			if a.GetName() == "deployment_branch" {
				releaseTrain = a.GetStringValue()
			}
		}
		releaseTrains[releaseTrain] = append(releaseTrains[releaseTrain], t.Target.Rule.GetName())
	}
	if (len(releaseTrains)) == 0 {
		log.Println("No matching targets found")
		return
	}

	for train, targets := range releaseTrains {
		fmt.Println(train)
		for _, t := range targets {
			fmt.Println(" ", t)
		}
	}

	gitopsdir, err := os.MkdirTemp(*gitopsTmpDir, "gitops")
	if err != nil {
		log.Fatalf("Unable to create tempdir in %s: %v", *gitopsTmpDir, err)
	}
	defer os.RemoveAll(gitopsdir)
	workdir, err := git.Clone(*repo, gitopsdir, *gitMirror, *prInto, *gitopsPath)
	if err != nil {
		log.Fatalf("Unable to clone repo: %v", err)
	}
	workdir.Fetch(*deploymentBranchPrefix + "*")

	var updatedGitopsBranches []string

	for train, targets := range releaseTrains {
		log.Println("train", train)
		branch := *deploymentBranchPrefix + train + *deploymentBranchSuffix
		newBranch := workdir.SwitchToBranch(branch, *prInto)
		if !newBranch {
			// Find if we need to recreate the branch because target was deleted
			msg := workdir.GetLastCommitMessage()
			targetset := make(map[string]bool)
			for _, t := range targets {
				targetset[t] = true
			}
			oldtargets := commitmsg.ExtractTargets(msg)
			for _, t := range oldtargets {
				if !targetset[t] {
					// target t is not present in a new list
					workdir.RecreateBranch(branch, *prInto)
					break
				}
			}
		}
		for _, target := range targets {
			log.Println("train", train, "target", target)
			bin := bazel.TargetToExecutable(target)
			exec.Mustex("", bin, "--deployment_root", gitopsdir)
		}
		if *stamp {
			changedFiles := workdir.GetChangedFiles()
			if len(changedFiles) > 0 {
				ctx := getGitStatusDict(workdir, *gitCommit, *branchName)
				for _, filePath := range changedFiles {
					fullPath := gitopsdir + "/" + filePath
					if digester.VerifyDigest(fullPath) {
						workdir.RestoreFile(fullPath)
					} else {
						digester.SaveDigest(fullPath)
						stampFile(fullPath, ctx)
					}
				}
			}
		}
		if workdir.Commit(fmt.Sprintf("GitOps for release branch %s from %s commit %s\n%s", *releaseBranch, *branchName, *gitCommit, commitmsg.Generate(targets, *branchName, *gitCommit)), *gitopsPath) {
			log.Println("branch", branch, "has changes, push is required")
			updatedGitopsBranches = append(updatedGitopsBranches, branch)
		}
	}
	if len(updatedGitopsBranches) == 0 {
		log.Println("No gitops changes to push")
		return
	}

	if *dryRun {
		log.Println("dry-run: updated gitops branches: ", updatedGitopsBranches)
		log.Println("dry-run: skipping push")
	} else {
		workdir.Push(updatedGitopsBranches)
	}

	for _, branch := range updatedGitopsBranches {
		if *dryRun {
			log.Println("dry-run: skipping PR creation: branch ", branch, "into ", *prInto)
			continue
		}

		title := *prTitle
		if title == "" {
			title = fmt.Sprintf("GitOps deployment %s", branch)
		}

		body := *prBody
		if body == "" {
			body = branch
		}

		if err := gitServer.CreatePR(branch, *prInto, title, body); err != nil {
			log.Fatal("unable to create PR: ", err)
		}
	}
}


================================================
FILE: gitops/private/BUILD.bazel
================================================
load("@bazel_lib//:bzl_library.bzl", "bzl_library")

package(
    default_visibility = ["//gitops:__subpackages__"],
)

exports_files([
    "nameprefix_deployment_labels_config.yaml",
    "namesuffix_deployment_labels_config.yaml",
    "k8s_gitops.sh.tpl",
])

bzl_library(
    name = "k8s_deploy",
    srcs = ["k8s_deploy.bzl"],
    deps = [
        ":gitops",
        "//kubectl:defs",
        "//kustomize:defs",
    ],
)

bzl_library(
    name = "gitops",
    srcs = ["gitops.bzl"],
    deps = [
        "//adapters:providers",
        "//kustomize:defs",
    ],
)


================================================
FILE: gitops/private/gitops.bzl
================================================
"""GitOps rule for generating deployment manifests."""

load("//adapters:providers.bzl", "K8sPushInfo")
load("//kustomize:defs.bzl", "KustomizeInfo")

# Convert short_path to runfiles manifest path for use with rlocation
def _to_manifest_path(ctx, file):
    if file.short_path.startswith("../"):
        return "external/" + file.short_path[3:]
    else:
        return ctx.workspace_name + "/" + file.short_path

def _image_push_statements(
        ctx,
        kustomize_objs,
        files = []):
    statements = ""
    trans_img_pushes = depset(transitive = [obj[KustomizeInfo].image_pushes for obj in kustomize_objs]).to_list()

    statements += "\n".join([
        "echo  pushing {}/{}".format(exe[K8sPushInfo].registry, exe[K8sPushInfo].repository)
        for exe in trans_img_pushes
        if hasattr(exe[K8sPushInfo], "pusher")
    ]) + "\n"
    statements += "\n".join([
        "  async \"$(rlocation %s)\"" % _to_manifest_path(ctx, exe[K8sPushInfo].pusher.files_to_run.executable)
        for exe in trans_img_pushes
        if hasattr(exe[K8sPushInfo], "pusher")
    ]) + "\n  waitpids\n"

    # files += [obj.files_to_run.executable for obj in trans_img_pushes]
    dep_runfiles = [obj[K8sPushInfo].pusher.default_runfiles for obj in trans_img_pushes if hasattr(obj[K8sPushInfo], "pusher")]
    return statements, files, dep_runfiles

def _remove_prefix(s, prefix):
    return s[len(prefix):] if s.startswith(prefix) else s

def _remove_prefixes(s, prefixes):
    for prefix in prefixes:
        s = _remove_prefix(s, prefix)
    return s

def _gitops_impl(ctx):
    cluster = ctx.attr.cluster
    strip_prefixes = ctx.attr.strip_prefixes
    files = []

    push_statements, files, pushes_runfiles = _image_push_statements(ctx, ctx.attr.srcs, files)
    statements = ""
    namespace = ctx.attr.namespace
    for inattr in ctx.attr.srcs:
        if "{" in namespace:
            fail("unable to gitops namespace with placeholders %s" % inattr.label)  #mynamespace should not be gitopsed
        for infile in inattr.files.to_list():
            statements += ("echo $TARGET_DIR/{gitops_path}/{namespace}/{cluster}/{file}\n" +
                           "mkdir -p $TARGET_DIR/{gitops_path}/{namespace}/{cluster}\n" +
                           "echo '# GENERATED BY {rulename} -> {gitopsrulename}' > $TARGET_DIR/{gitops_path}/{namespace}/{cluster}/{file}\n" +
                           "{template_engine} --template={infile} --variable=NAMESPACE={namespace} --stamp_info_file={info_file} >> $TARGET_DIR/{gitops_path}/{namespace}/{cluster}/{file}\n").format(
                infile = infile.path,
                rulename = inattr.label,
                gitopsrulename = ctx.label,
                namespace = namespace,
                gitops_path = ctx.attr.gitops_path,
                cluster = cluster,
                file = _remove_prefixes(infile.path.split("/")[-1], strip_prefixes),
                template_engine = ctx.executable._template_engine.path,
                info_file = ctx.file._info_file.path,
            )

    ctx.actions.expand_template(
        template = ctx.file._template,
        substitutions = {
            "%{deployment_branch}": ctx.attr.deployment_branch,
            "%{push_statements}": push_statements,
            "%{statements}": statements,
        },
        output = ctx.outputs.executable,
    )
    runfiles = files + ctx.files.srcs + [ctx.executable._template_engine, ctx.file._info_file]
    transitive = depset(transitive = [obj.default_runfiles.files for obj in ctx.attr.srcs])

    rf = ctx.runfiles(files = runfiles, transitive_files = transitive).merge(
        ctx.attr._bash_runfiles[DefaultInfo].default_runfiles,
    )
    for dep_rf in pushes_runfiles:
        rf = rf.merge(dep_rf)
    return [
        DefaultInfo(runfiles = rf),
        KustomizeInfo(
            image_pushes = depset(transitive = [obj[KustomizeInfo].image_pushes for obj in ctx.attr.srcs]),
        ),
    ]

gitops = rule(
    attrs = {
        "srcs": attr.label_list(providers = (KustomizeInfo,)),
        "cluster": attr.string(mandatory = True),
        "namespace": attr.string(mandatory = True),
        "deployment_branch": attr.string(),
        "gitops_path": attr.string(),
        "release_branch_prefix": attr.string(),
        "strip_prefixes": attr.string_list(),
        "_info_file": attr.label(
            default = Label("//stamper:more_stable_status.txt"),
            allow_single_file = True,
        ),
        "_template_engine": attr.label(
            default = Label("//templating:fast_template_engine"),
            executable = True,
            cfg = "exec",
        ),
        "_template": attr.label(
            default = Label("//gitops/private:k8s_gitops.sh.tpl"),
            allow_single_file = True,
        ),
        "_bash_runfiles": attr.label(
            default = Label("@bazel_tools//tools/bash/runfiles"),
        ),
    },
    executable = True,
    implementation = _gitops_impl,
)


================================================
FILE: gitops/private/k8s_deploy.bzl
================================================
"""Macro for creating Kubernetes deployment targets with GitOps support."""

load("//gitops/private:gitops.bzl", _gitops = "gitops")
load("//kubectl:defs.bzl", "kubectl_binary")
load("//kustomize:defs.bzl", "kustomization", "show")

def k8s_deploy(
        name,  # name of the rule is important for gitops, since it will become a part of the target manifest file name in /cloud
        cluster = "dev",
        user = "{BUILD_USER}",
        namespace = None,
        configmaps_srcs = None,
        secrets_srcs = None,
        configmaps_renaming = None,  # configmaps renaming policy. Could be None or 'hash'.
        manifests = None,
        name_prefix = None,
        name_suffix = None,
        prefix_suffix_app_labels = False,  # apply kustomize configuration to modify "app" labels in Deployments when name prefix or suffix applied
        patches = None,
        image_name_patches = {},
        image_tag_patches = {},
        substitutions = {},  # dict of template parameter substitutions. CLUSTER and NAMESPACE parameters are added automatically.
        configurations = [],  # additional kustomize configuration files. rules_gitops provides
        common_labels = {},  # list of common labels to apply to all objects see commonLabels kustomize docs
        common_annotations = {},  # list of common annotations to apply to all objects see commonAnnotations kustomize docs
        deps = [],
        deps_aliases = {},
        images = [],
        objects = [],
        gitops = True,  # make sure to use gitops = False to work with individual namespace. This option will be turned False if namespace is '{BUILD_USER}'
        gitops_path = "cloud",
        deployment_branch = None,
        release_branch_prefix = "main",
        start_tag = "{{",
        end_tag = "}}",
        tags = [],
        visibility = None,
        verify_images = True):
    """Generates Kubernetes deployment targets with optional GitOps support.

    This macro creates kustomization targets and kubectl binaries for deploying
    Kubernetes manifests. When gitops is enabled, it also creates a gitops target
    for managing deployments through a GitOps workflow.

    Args:
        name: Name of the rule. Important for gitops since it becomes part of
            the target manifest file name in the gitops_path directory.
        cluster: Target Kubernetes cluster name. Defaults to "dev".
        user: Kubernetes user for authentication. Defaults to "{BUILD_USER}"
            which is substituted at runtime.
        namespace: Target Kubernetes namespace. Required when gitops=True.
            Defaults to "{BUILD_USER}" when gitops=False.
        configmaps_srcs: List of source files for generating ConfigMaps.
        secrets_srcs: List of source files for generating Secrets.
        configmaps_renaming: ConfigMap renaming policy. Can be None (no renaming)
            or "hash" (append content hash to names).
        manifests: List of Kubernetes manifest files. Defaults to all .yaml and
            .yaml.tpl files in the package via glob.
        name_prefix: Prefix to add to all resource names via kustomize.
        name_suffix: Suffix to add to all resource names via kustomize.
        prefix_suffix_app_labels: If True, applies kustomize configuration to
            modify "app" labels in Deployments when name_prefix or name_suffix
            is applied.
        patches: List of kustomize patches to apply to the manifests.
        image_name_patches: Dict mapping original image names to new image names.
        image_tag_patches: Dict mapping image names to new tags.
        substitutions: Dict of template parameter substitutions. CLUSTER and
            NAMESPACE parameters are added automatically and should not be
            included.
        configurations: List of additional kustomize configuration files.
        common_labels: Dict of labels to apply to all Kubernetes objects.
            See kustomize commonLabels documentation.
        common_annotations: Dict of annotations to apply to all Kubernetes
            objects. See kustomize commonAnnotations documentation.
        deps: List of dependency targets.
        deps_aliases: Dict mapping aliases to dependency targets.
        images: List of container image targets to include in the deployment.
        objects: List of additional Kubernetes objects to include.
        gitops: If True, creates a gitops target for GitOps workflow. Set to
            False to work with individual namespaces. Automatically set to
            False if namespace is "{BUILD_USER}".
        gitops_path: Directory path for gitops manifests. Defaults to "cloud".
        deployment_branch: Git branch for deployments. If None, uses default.
        release_branch_prefix: Prefix for release branches. Defaults to "main".
        start_tag: Opening delimiter for template substitutions. Defaults to "{{".
        end_tag: Closing delimiter for template substitutions. Defaults to "}}".
        tags: List of tags to apply to all generated targets.
        visibility: Visibility specification for generated targets.
        verify_images: Whether or not to fail if a Bazel image could not be resolved.
    """

    if not manifests:
        manifests = native.glob(["*.yaml", "*.yaml.tpl"])
    if prefix_suffix_app_labels:
        configurations = configurations + [
            str(Label("//gitops:nameprefix_deployment_labels_config.yaml")),
            str(Label("//gitops:namesuffix_deployment_labels_config.yaml")),
        ]
    for reservedname in ["CLUSTER", "NAMESPACE"]:
        if substitutions.get(reservedname):
            fail("do not put %s in substitutions parameter of k8s_deploy. It will be added autimatically" % reservedname)
    substitutions = dict(substitutions)
    substitutions["CLUSTER"] = cluster

    # NAMESPACE substitution is deferred until test_setup/kubectl/gitops
    if namespace == "{BUILD_USER}":
        gitops = False

    if not gitops:
        # Mynamespace option
        if not namespace:
            namespace = "{BUILD_USER}"
        kustomization(
            name = name,
            namespace = namespace,
            configmaps_srcs = configmaps_srcs,
            secrets_srcs = secrets_srcs,
            # disable_name_suffix_hash is renamed to configmaps_renaming in recent Kustomize
            disable_name_suffix_hash = (configmaps_renaming != "hash"),
            images = images,
            manifests = manifests,
            substitutions = substitutions,
            deps = deps,
            deps_aliases = deps_aliases,
            start_tag = start_tag,
            end_tag = end_tag,
            name_prefix = name_prefix,
            name_suffix = name_suffix,
            configurations = configurations,
            common_labels = common_labels,
            common_annotations = common_annotations,
            patches = patches,
            objects = objects,
            image_name_patches = image_name_patches,
            image_tag_patches = image_tag_patches,
            tags = tags,
            visibility = visibility,
            verify_images = verify_images,
        )
        kubectl_binary(
            name = name + ".apply",
            srcs = [name],
            cluster = cluster,
            user = user,
            namespace = namespace,
            tags = tags,
            visibility = visibility,
        )
        kubectl_binary(
            name = name + ".delete",
            srcs = [name],
            command = "delete",
            cluster = cluster,
            push = False,
            user = user,
            namespace = namespace,
            tags = tags,
            visibility = visibility,
        )
        show(
            name = name + ".show",
            namespace = namespace,
            src = name,
            tags = tags,
            visibility = visibility,
        )
    else:
        # gitops
        if not namespace:
            fail("namespace must be defined for gitops k8s_deploy")
        kustomization(
            name = name,
            namespace = namespace,
            configmaps_srcs = configmaps_srcs,
            secrets_srcs = secrets_srcs,
            # disable_name_suffix_hash is renamed to configmaps_renaming in recent Kustomize
            disable_name_suffix_hash = (configmaps_renaming != "hash"),
            images = images,
            manifests = manifests,
            visibility = visibility,
            substitutions = substitutions,
            deps = deps,
            deps_aliases = deps_aliases,
            start_tag = start_tag,
            end_tag = end_tag,
            name_prefix = name_prefix,
            name_suffix = name_suffix,
            configurations = configurations,
            common_labels = common_labels,
            common_annotations = common_annotations,
            patches = patches,
            image_name_patches = image_name_patches,
            image_tag_patches = image_tag_patches,
            tags = tags,
        )
        kubectl_binary(
            name = name + ".apply",
            srcs = [name],
            cluster = cluster,
            user = user,
            namespace = namespace,
            tags = tags,
            visibility = visibility,
        )
        _gitops(
            name = name + ".gitops",
            srcs = [name],
            cluster = cluster,
            namespace = namespace,
            gitops_path = gitops_path,
            strip_prefixes = [
                namespace + "-",
                cluster + "-",
            ],
            deployment_branch = deployment_branch,
            release_branch_prefix = release_branch_prefix,
            tags = tags,
            visibility = ["//visibility:public"],
        )
        show(
            name = name + ".show",
            src = name,
            namespace = namespace,
            tags = tags,
            visibility = visibility,
        )


================================================
FILE: gitops/private/k8s_gitops.sh.tpl
================================================
#!/usr/bin/env bash
# Copyright 2026 Adobe. All rights reserved.
# This file is licensed to you 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 REPRESENTATIONS
# OF ANY KIND, either express or implied. See the License for the specific language
# governing permissions and limitations under the License.

set -o nounset
set -o pipefail

# --- begin runfiles.bash initialization v3 ---
# Copy-pasted from the Bazel Bash runfiles library v3.
set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash
# shellcheck disable=SC1090
source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \
source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \
source "$0.runfiles/$f" 2>/dev/null || \
source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
{ echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
# --- end runfiles.bash initialization v3 ---

runfiles_export_envvars

DEPLOYMENT_ROOT=""
PERFORM_PUSH="1"
# parse command line parameters
while [[ $# -gt 0 ]]
do
  key="$1"
  case $key in
    -r|--deployment_root|--deployment-root)
    DEPLOYMENT_ROOT="$2"
    shift # past argument
    shift # past value
    ;;
    --nopush)
    PERFORM_PUSH=""
    shift
    ;;
    *)    # unknown option
    echo Unsupported parameter $1
    exit 1
    ;;
  esac
done

PIDS=()
function async() {
    # Launch the command asynchronously and track its process id.
    "$@" &
    PIDS+=($!)
}

function waitpids() {
  # Wait for all of the subprocesses, returning the exit code of the first failed process.
  if [ "${#PIDS[@]}" != 0 ]; then
    for pid in ${PIDS[@]}; do
      wait ${pid} || return $?
    done
  fi
}

if [ "$PERFORM_PUSH" == "1" ]; then
  %{push_statements}
fi

cd $BUILD_WORKSPACE_DIRECTORY

if [ "%{deployment_branch}" != "" -a "${DEPLOYMENT_ROOT}" != "" ] ; then
  TARGET_DIR=${DEPLOYMENT_ROOT}
else
  echo "--deployment-root or deployment_branch is not specified, using repo root"
  TARGET_DIR=$BUILD_WORKSPACE_DIRECTORY
fi

# make sure that the script exits immediately if any command below fails
set -o errexit
%{statements}


================================================
FILE: gitops/private/nameprefix_deployment_labels_config.yaml
================================================

namePrefix:
- path: metadata/name
- path: spec/selector/matchLabels/app
  kind: Deployment
- path: spec/template/metadata/labels/app
  kind: Deployment
- path: spec/selector/matchLabels/app.kubernetes.io\/name
  kind: Deployment
- path: spec/template/metadata/labels/app.kubernetes.io\/name
  kind: Deployment
- path: spec/selector/app
  kind: Service
- path: spec/selector/app.kubernetes.io\/name
  kind: Service


================================================
FILE: gitops/private/namesuffix_deployment_labels_config.yaml
================================================

nameSuffix:
  - path: metadata/name
  - path: spec/selector/matchLabels/app
    kind: Deployment
  - path: spec/template/metadata/labels/app
    kind: Deployment
  - path: spec/selector/matchLabels/app.kubernetes.io\/name
    kind: Deployment
  - path: spec/template/metadata/labels/app.kubernetes.io\/name
    kind: Deployment
  - path: spec/selector/app
    kind: Service
  - path: spec/selector/app.kubernetes.io\/name
    kind: Service


================================================
FILE: gitops/private/test/BUILD.bazel
================================================
load("@bazel_lib//:bzl_library.bzl", "bzl_library")

# Copyright 2026 Adobe. All rights reserved.
# This file is licensed to you 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 REPRESENTATIONS
# OF ANY KIND, either express or implied. See the License for the specific language
# governing permissions and limitations under the License.

load("//adapters:external_image.bzl", "external_image")
load("//gitops:defs.bzl", "k8s_deploy")
load("//tools:util.bzl", "golden_test")

licenses(["notice"])  # Apache 2.0

k8s_deploy(
    name = "deploy_test",
    cluster = "testcluster",
    deployment_branch = "test1",
    gitops = True,
    images = ["//gitops/private/test/images:k8s_image"],
    manifests = [
        ":deployment.yaml",
    ],
    namespace = "ci",
    release_branch_prefix = "gitops_test_release_branch",
    visibility = ["//visibility:public"],
)

golden_test(
    name = "deploy_test_golden",
    in_file = "deploy_test",
)

external_image(
    name = "external_image",
    digest = "sha:1234567890",
    image = "gcr.io/repo/someimage:thetag",
)

k8s_deploy(
    name = "external_image_test",
    cluster = "testcluster",
    deployment_branch = "test1",
    gitops = True,
    images = [
        ":external_image",
    ],
    manifests = [
        ":deployment1.yaml",
    ],
    namespace = "ci",
    release_branch_prefix = "gitops_test_release_branch",
    visibility = ["//visibility:public"],
)

golden_test(
    name = "external_image_golden",
    in_file = "external_image_test",
)

bzl_library(
    name = "templates",
    srcs = ["templates.bzl"],
    visibility = ["//gitops:__subpackages__"],
)


================================================
FILE: gitops/private/test/deployment.yaml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: //gitops/private/test/images:image


================================================
FILE: gitops/private/test/deployment1.yaml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: //gitops/private/test:external_image


================================================
FILE: gitops/private/test/goldens/deploy_test.golden
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  namespace: ci
spec:
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - image: localhost:15000/rules_gitops/gitops/private/test/images/helloworld@sha256:c361bf07a58f30284effb92ee1c6504b75428c5019e8a60a27120038a20e9c3e
        name: myapp


================================================
FILE: gitops/private/test/goldens/external_image_test.golden
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  namespace: ci
spec:
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - image: gcr.io/repo/someimage@sha:1234567890
        name: myapp


================================================
FILE: gitops/private/test/images/BUILD.bazel
================================================
load("@rules_gitops//adapters:rules_img.bzl", "k8s_push_info")
load("@rules_img//img:image.bzl", "image_manifest")
load("@rules_img//img:layer.bzl", "image_layer")
load("@rules_img//img:load.bzl", "image_load")
load("@rules_img//img:push.bzl", "image_push")

package(
    default_visibility = ["//gitops/private/test:__subpackages__"],
)

REGISTRY = "localhost:15000"

platform(
    name = "linux_amd64",
    constraint_values = [
        "@platforms//os:linux",
        "@platforms//cpu:x86_64",
    ],
)

image_layer(
    name = "layer",
    srcs = {
        "/bin/app": "container_content.txt",
    },
)

image_manifest(
    name = "image",
    base = "@alpine",
    entrypoint = ["/bin/app"],
    layers = [
        ":layer",
    ],
    platform = ":linux_amd64",
)

image_load(
    name = "load",
    image = ":image",
    tag = "helloworld:latest",
    visibility = ["//:__pkg__"],
)

image_push(
    name = "push",
    image = ":image",
    registry = REGISTRY,
    repository = "rules_gitops/gitops/private/test/images/helloworld",
    tag = "native",
    visibility = ["//:__pkg__"],
)

k8s_push_info(
    name = 
Download .txt
gitextract_s1k_48ok/

├── .bazelignore
├── .bazelrc
├── .bazelversion
├── .bcr/
│   ├── metadata.template.json
│   ├── presubmit.yml
│   └── source.template.json
├── .gitattributes
├── .github/
│   ├── CODEOWNERS
│   ├── CONTRIBUTING.md
│   ├── ISSUE_TEMPLATE.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   └── workflows/
│       ├── ci.yaml
│       ├── conventional-commits.yaml
│       ├── publish.yaml
│       ├── release.yml
│       └── release_prep.sh
├── .gitignore
├── BUILD.bazel
├── CODE_OF_CONDUCT.md
├── COPYRIGHT
├── LICENSE
├── MODULE.bazel
├── README.md
├── WORKSPACE.bazel
├── adapters/
│   ├── BUILD.bazel
│   ├── external_image.bzl
│   ├── providers.bzl
│   └── rules_img.bzl
├── e2e/
│   ├── BUILD.bazel
│   ├── deployment/
│   │   ├── BUILD.bazel
│   │   ├── helloworld.go
│   │   └── manifests/
│   │       ├── deployment.yaml
│   │       └── service.yaml
│   └── util/
│       ├── BUILD.bazel
│       ├── kubectl_cmd.sh.tpl
│       ├── setup.sh
│       ├── teardown.sh
│       ├── test.sh
│       └── util.bzl
├── examples/
│   ├── README.md
│   ├── helloworld/
│   │   ├── .bazelrc
│   │   ├── .bazelversion
│   │   ├── BUILD.bazel
│   │   ├── MODULE.bazel
│   │   ├── deployment.yaml
│   │   ├── helloworld.go
│   │   ├── helloworld_test.go
│   │   ├── k8s_deploy_test.sh
│   │   └── service.yaml
│   └── legacy_docker/
│       ├── .bazelrc
│       ├── .bazelversion
│       ├── BUILD.bazel
│       ├── MODULE.bazel
│       ├── WORKSPACE
│       ├── adapters/
│       │   ├── BUILD.bazel
│       │   └── docker.bzl
│       ├── container_contents.txt
│       ├── deployment.yaml
│       └── patches/
│           └── docker.patch
├── gitops/
│   ├── BUILD.bazel
│   ├── analysis/
│   │   ├── BUILD.bazel
│   │   ├── analysis.go
│   │   └── analysis.proto
│   ├── bazel/
│   │   ├── BUILD.bazel
│   │   ├── bazeltargets.go
│   │   └── bazeltargets_test.go
│   ├── blaze_query/
│   │   ├── BUILD.bazel
│   │   └── build.proto
│   ├── commitmsg/
│   │   ├── BUILD.bazel
│   │   ├── commitmsg.go
│   │   └── commitmsg_test.go
│   ├── defs.bzl
│   ├── digester/
│   │   ├── BUILD.bazel
│   │   └── digester.go
│   ├── exec/
│   │   ├── BUILD
│   │   ├── BUILD.bazel
│   │   └── exec.go
│   ├── git/
│   │   ├── BUILD.bazel
│   │   ├── bitbucket/
│   │   │   ├── BUILD.bazel
│   │   │   ├── bitbucket.go
│   │   │   ├── bitbucket_test.go
│   │   │   └── testdata/
│   │   │       └── create_pr.json
│   │   ├── git.go
│   │   ├── github/
│   │   │   ├── BUILD.bazel
│   │   │   └── github.go
│   │   ├── gitlab/
│   │   │   ├── BUILD.bazel
│   │   │   ├── gitlab.go
│   │   │   └── gitlab_test.go
│   │   └── server.go
│   ├── prer/
│   │   ├── BUILD.bazel
│   │   └── create_gitops_prs.go
│   └── private/
│       ├── BUILD.bazel
│       ├── gitops.bzl
│       ├── k8s_deploy.bzl
│       ├── k8s_gitops.sh.tpl
│       ├── nameprefix_deployment_labels_config.yaml
│       ├── namesuffix_deployment_labels_config.yaml
│       └── test/
│           ├── BUILD.bazel
│           ├── deployment.yaml
│           ├── deployment1.yaml
│           ├── goldens/
│           │   ├── deploy_test.golden
│           │   └── external_image_test.golden
│           ├── images/
│           │   ├── BUILD.bazel
│           │   └── container_content.txt
│           └── templates.bzl
├── go.mod
├── go.sum
├── kubectl/
│   ├── BUILD.bazel
│   ├── defs.bzl
│   └── private/
│       ├── BUILD.bazel
│       ├── extension.bzl
│       ├── kubeconfig.bzl
│       ├── kubectl_binary.bzl
│       ├── platforms.bzl
│       ├── providers.bzl
│       ├── resolved_toolchain.bzl
│       ├── run-all.sh.tpl
│       ├── toolchain.bzl
│       └── versions/
│           ├── BUILD.bazel
│           ├── update_versions.go
│           └── versions.bzl
├── kustomize/
│   ├── BUILD.bazel
│   ├── defs.bzl
│   └── private/
│       ├── BUILD.bazel
│       ├── extension.bzl
│       ├── kustomization.bzl
│       ├── kustomize_binary.bzl
│       ├── platforms.bzl
│       ├── providers.bzl
│       ├── resolved_toolchain.bzl
│       ├── show.bzl
│       ├── tests/
│       │   ├── BUILD.bazel
│       │   ├── crb.yaml
│       │   ├── deployment.yaml
│       │   ├── goldens/
│       │   │   └── raw.golden
│       │   └── service.yaml
│       ├── toolchain.bzl
│       └── versions/
│           ├── BUILD.bazel
│           ├── update_versions.go
│           └── versions.bzl
├── renovate.json
├── resolver/
│   ├── BUILD.bazel
│   ├── pkg/
│   │   ├── BUILD.bazel
│   │   ├── resolver.go
│   │   ├── resolver_test.go
│   │   └── testdata/
│   │       ├── cwf.expected.yaml
│   │       ├── cwf.yaml
│   │       ├── digest.expected.yaml
│   │       ├── digest.yaml
│   │       ├── emptyinit.expected.yaml
│   │       ├── emptyinit.yaml
│   │       ├── flinkapp.expected.yaml
│   │       ├── flinkapp.yaml
│   │       ├── happypath.expected.yaml
│   │       ├── happypath.yaml
│   │       ├── zk.expected.yaml
│   │       └── zk.yaml
│   └── resolver.go
├── stamper/
│   ├── BUILD.bazel
│   ├── main.go
│   └── stamp.bzl
├── templating/
│   ├── BUILD.bazel
│   ├── fasttemplate/
│   │   ├── BUILD.bazel
│   │   ├── LICENSE
│   │   ├── README.md
│   │   ├── example_test.go
│   │   ├── template.go
│   │   └── template_test.go
│   ├── main.go
│   └── testdata/
│       ├── generated
│       ├── stable-status.txt
│       ├── template1.tpl
│       └── volatile-status.txt
├── testing/
│   ├── BUILD.bazel
│   ├── defs.bzl
│   ├── it_manifest_filter/
│   │   ├── BUILD.bazel
│   │   ├── it_manifest_filter.go
│   │   └── pkg/
│   │       ├── BUILD.bazel
│   │       ├── filter.go
│   │       ├── filter_test.go
│   │       └── testdata/
│   │           ├── certificate.expected.yaml
│   │           ├── certificate.yaml
│   │           ├── happypath.expected.yaml
│   │           ├── happypath.yaml
│   │           ├── statefulset.expected.yaml
│   │           ├── statefulset.yaml
│   │           ├── statefulset2.expected.yaml
│   │           ├── statefulset2.yaml
│   │           ├── statefulset3.expected.yaml
│   │           └── statefulset3.yaml
│   ├── it_sidecar/
│   │   ├── BUILD.bazel
│   │   ├── client/
│   │   │   ├── BUILD.bazel
│   │   │   ├── noop.setup
│   │   │   ├── sidecar_client.go
│   │   │   ├── test_callback/
│   │   │   │   ├── BUILD.bazel
│   │   │   │   └── sidecar_client_test.go
│   │   │   └── test_no_callback/
│   │   │       ├── BUILD.bazel
│   │   │       └── sidecar_client_test_no_callback.go
│   │   ├── it_sidecar.go
│   │   └── stern/
│   │       ├── BUILD.bazel
│   │       ├── container_state.go
│   │       ├── main.go
│   │       ├── tail.go
│   │       └── watch.go
│   └── private/
│       ├── BUILD.bazel
│       ├── k8s_test_namespace.bzl
│       ├── k8s_test_namespace.sh.tpl
│       ├── k8s_test_setup.bzl
│       └── set_namespace.sh
├── tests/
│   ├── BUILD.bazel
│   ├── configmaps/
│   │   ├── mapa/
│   │   │   ├── mapa1.properties
│   │   │   └── mapa2.properties
│   │   └── mapb/
│   │       └── config.yaml
│   ├── crb.yaml
│   ├── deployment.yaml
│   ├── deployment_with_labels.yaml
│   ├── expected_image_resolved_test.tpl.yaml
│   ├── expected_patch.tpl.yaml
│   ├── goldens/
│   │   ├── common_labels.golden.yaml
│   │   ├── configmap.golden.yaml
│   │   ├── deployment_prefix.golden.yaml
│   │   ├── deployment_prefix_compat.golden.yaml
│   │   ├── deployment_suffix.golden.yaml
│   │   ├── deployment_suffix_compat.golden.yaml
│   │   ├── image_digest.golden.yaml
│   │   ├── image_resolved.golden.yaml
│   │   ├── name_prefix.golden.yaml
│   │   ├── name_suffix.golden.yaml
│   │   ├── namespace.golden.yaml
│   │   ├── patch.golden.yaml
│   │   ├── patch_images.golden.yaml
│   │   ├── raw.golden.yaml
│   │   └── secrets.golden.yaml
│   ├── images/
│   │   ├── BUILD.bazel
│   │   └── container_content.txt
│   ├── job.yaml
│   ├── overlay/
│   │   └── deployment.yaml
│   ├── secrets/
│   │   ├── secreta/
│   │   │   └── file1.yaml
│   │   └── secretb/
│   │       ├── file2.txt
│   │       └── foo
│   ├── service.yaml
│   ├── set_namespace_test.sh
│   ├── test.yaml
│   └── test_expected.yaml
└── tools/
    ├── BUILD.bazel
    ├── bazel
    ├── preset.bzl
    ├── preset7.bazelrc
    ├── preset8.bazelrc
    ├── preset9.bazelrc
    └── util.bzl
Download .txt
SYMBOL INDEX (164 symbols across 38 files)

FILE: e2e/deployment/helloworld.go
  function printenv (line 27) | func printenv(w http.ResponseWriter, r *http.Request) {
  function home (line 33) | func home(w http.ResponseWriter, r *http.Request) {
  function main (line 37) | func main() {

FILE: examples/helloworld/helloworld.go
  function printenv (line 27) | func printenv(w http.ResponseWriter, r *http.Request) {
  function home (line 33) | func home(w http.ResponseWriter, r *http.Request) {
  function main (line 37) | func main() {

FILE: examples/helloworld/helloworld_test.go
  function TestHome (line 21) | func TestHome(t *testing.T) {

FILE: gitops/bazel/bazeltargets.go
  function TargetToExecutable (line 17) | func TargetToExecutable(target string) string {

FILE: gitops/bazel/bazeltargets_test.go
  function TestTargetToExecutableHappypath (line 16) | func TestTargetToExecutableHappypath(t *testing.T) {

FILE: gitops/commitmsg/commitmsg.go
  constant begin (line 19) | begin = "--- gitops targets begin ---"
  constant end (line 20) | end = "--- gitops targets end ---"
  function ExtractTargets (line 23) | func ExtractTargets(msg string) (packages []string) {
  function Generate (line 44) | func Generate(targets []string, branchName, gitCommit string) string {

FILE: gitops/commitmsg/commitmsg_test.go
  function TestRoundtrip (line 22) | func TestRoundtrip(t *testing.T) {
  function ExampleGenerate (line 31) | func ExampleGenerate() {

FILE: gitops/digester/digester.go
  function CalculateDigest (line 24) | func CalculateDigest(path string) string {
  function GetDigest (line 44) | func GetDigest(path string) string {
  function VerifyDigest (line 60) | func VerifyDigest(path string) bool {
  function SaveDigest (line 65) | func SaveDigest(path string) {

FILE: gitops/exec/exec.go
  function Ex (line 21) | func Ex(dir, name string, arg ...string) (output string, err error) {
  function Mustex (line 34) | func Mustex(dir, name string, arg ...string) {

FILE: gitops/git/bitbucket/bitbucket.go
  type project (line 31) | type project struct
  type repository (line 35) | type repository struct
  type pullrequestEndpoint (line 40) | type pullrequestEndpoint struct
  type account (line 45) | type account struct
  type user (line 49) | type user struct
  type pullrequest (line 53) | type pullrequest struct
  function CreatePR (line 66) | func CreatePR(from, to, title, body string) error {

FILE: gitops/git/bitbucket/bitbucket_test.go
  function TestCreatePRRemote (line 22) | func TestCreatePRRemote(t *testing.T) {
  function TestCreatePRNew (line 34) | func TestCreatePRNew(t *testing.T) {

FILE: gitops/git/git.go
  function Clone (line 37) | func Clone(repo, dir, mirrorDir, primaryBranch, gitopsPath string) (*Rep...
  type Repo (line 65) | type Repo struct
    method Clean (line 73) | func (r *Repo) Clean() error {
    method Fetch (line 79) | func (r *Repo) Fetch(pattern string) {
    method SwitchToBranch (line 86) | func (r *Repo) SwitchToBranch(branch, primaryBranch string) (new bool) {
    method RecreateBranch (line 97) | func (r *Repo) RecreateBranch(branch, primaryBranch string) {
    method GetLastCommitMessage (line 104) | func (r *Repo) GetLastCommitMessage() (msg string) {
    method Commit (line 113) | func (r *Repo) Commit(message, gitopsPath string) bool {
    method RestoreFile (line 127) | func (r *Repo) RestoreFile(fileName string) {
    method GetChangedFiles (line 132) | func (r *Repo) GetChangedFiles() []string {
    method IsClean (line 149) | func (r *Repo) IsClean() bool {
    method Push (line 161) | func (r *Repo) Push(branches []string) {
  function isRootPath (line 167) | func isRootPath(gitopsPath string) bool {

FILE: gitops/git/github/github.go
  function CreatePR (line 23) | func CreatePR(from, to, title, body string) error {

FILE: gitops/git/gitlab/gitlab.go
  function CreatePR (line 20) | func CreatePR(from, to, title, body string) error {

FILE: gitops/git/gitlab/gitlab_test.go
  function TestCreatePRRemote (line 5) | func TestCreatePRRemote(t *testing.T) {

FILE: gitops/git/server.go
  type Server (line 3) | type Server interface
  type ServerFunc (line 7) | type ServerFunc
    method CreatePR (line 9) | func (f ServerFunc) CreatePR(from, to, title, body string) error {

FILE: gitops/prer/create_gitops_prs.go
  function init (line 37) | func init() {
  function bazelQuery (line 62) | func bazelQuery(query string) *analysis.CqueryResult {
  function getGitStatusDict (line 83) | func getGitStatusDict(workdir *git.Repo, gitCommit, branchName string) m...
  function stampFile (line 99) | func stampFile(fullPath string, ctx map[string]interface{}) {
  function main (line 117) | func main() {

FILE: kubectl/private/versions/update_versions.go
  constant dlBaseURL (line 25) | dlBaseURL  = "https://dl.k8s.io/release/"
  constant outputFile (line 26) | outputFile = "kubectl/private/versions/versions.bzl"
  type VersionInfo (line 41) | type VersionInfo struct
  function main (line 45) | func main() {
  function discoverStableVersions (line 80) | func discoverStableVersions() []string {
  function parseMinorVersion (line 123) | func parseMinorVersion(version string) int {
  function fetchStableVersion (line 134) | func fetchStableVersion(filename string) string {
  function fetchAllChecksums (line 163) | func fetchAllChecksums(versions []string) map[string]*VersionInfo {
  function fetchSHA256 (line 204) | func fetchSHA256(version, os, arch string) (string, error) {
  function findLatestVersion (line 240) | func findLatestVersion(versions []string) string {
  function compareVersions (line 256) | func compareVersions(v1, v2 string) int {
  function generateBzl (line 275) | func generateBzl(versions map[string]*VersionInfo, latestVersion string)...
  function getOutputPath (line 316) | func getOutputPath() string {

FILE: kustomize/private/versions/update_versions.go
  constant releasesURL (line 26) | releasesURL = "https://api.github.com/repos/kubernetes-sigs/kustomize/re...
  constant outputFile (line 27) | outputFile  = "kustomize/private/versions/versions.bzl"
  type Asset (line 31) | type Asset struct
  type Release (line 38) | type Release struct
  type VersionInfo (line 45) | type VersionInfo struct
  function main (line 49) | func main() {
  function fetchReleases (line 78) | func fetchReleases() ([]Release, error) {
  function getNextPageURL (line 115) | func getNextPageURL(linkHeader string) string {
  function parseReleases (line 134) | func parseReleases(releases []Release) (map[string]*VersionInfo, string) {
  function generateBzl (line 187) | func generateBzl(versions map[string]*VersionInfo, latestVersion string)...
  function getOutputPath (line 226) | func getOutputPath() string {

FILE: resolver/pkg/resolver.go
  function ResolveImages (line 26) | func ResolveImages(in io.Reader, out io.Writer, imgmap map[string]string...
  function isEmptyYamlError (line 68) | func isEmptyYamlError(err error) bool {
  type imageTagTransformer (line 72) | type imageTagTransformer struct
    method findAndReplaceTag (line 81) | func (pt *imageTagTransformer) findAndReplaceTag(obj map[string]interf...
    method updateContainers (line 114) | func (pt *imageTagTransformer) updateContainers(obj map[string]interfa...
    method updateContainer (line 148) | func (pt *imageTagTransformer) updateContainer(obj map[string]interfac...
    method findContainers (line 168) | func (pt *imageTagTransformer) findContainers(obj map[string]interface...

FILE: resolver/pkg/resolver_test.go
  function TestNoError (line 25) | func TestNoError(t *testing.T) {

FILE: resolver/resolver.go
  type imagesFlags (line 25) | type imagesFlags
    method String (line 27) | func (i *imagesFlags) String() string {
    method Set (line 31) | func (i *imagesFlags) Set(value string) error {
  function main (line 46) | func main() {

FILE: stamper/main.go
  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 init (line 41) | func init() {
  function workspaceStatusDict (line 48) | func workspaceStatusDict(filenames []string) map[string]interface{} {
  function main (line 65) | func main() {

FILE: templating/fasttemplate/example_test.go
  function ExampleTemplate (line 9) | func ExampleTemplate() {
  function ExampleTemplateWithSpaces (line 32) | func ExampleTemplateWithSpaces() {
  function ExampleTagFunc (line 56) | func ExampleTagFunc() {

FILE: templating/fasttemplate/template.go
  function executeFunc (line 23) | func executeFunc(template, startTag, endTag string, w io.Writer, f TagFu...
  function Execute (line 83) | func Execute(template, startTag, endTag string, w io.Writer, m map[strin...
  function executeFuncString (line 94) | func executeFuncString(template, startTag, endTag string, f TagFunc) str...
  function ExecuteString (line 117) | func ExecuteString(template, startTag, endTag string, m map[string]inter...
  type TagFunc (line 127) | type TagFunc
  function stdTagFunc (line 131) | func stdTagFunc(w io.Writer, tag string, m map[string]interface{}) (int,...

FILE: templating/fasttemplate/template_test.go
  function TestExecuteFunc (line 9) | func TestExecuteFunc(t *testing.T) {
  function testExecuteFunc (line 31) | func testExecuteFunc(t *testing.T, template, expectedOutput string) {
  function TestExecute (line 46) | func TestExecute(t *testing.T) {
  function testExecute (line 68) | func testExecute(t *testing.T, template, expectedOutput string) {
  function TestExecuteString (line 77) | func TestExecuteString(t *testing.T) {
  function testExecuteString (line 99) | func testExecuteString(t *testing.T, template, expectedOutput string) {
  function expectPanic (line 106) | func expectPanic(t *testing.T, f func()) {

FILE: templating/main.go
  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 init (line 43) | func init() {
  function workspaceStatusDict (line 54) | func workspaceStatusDict(filenames []string) map[string]interface{} {
  function main (line 71) | func main() {

FILE: testing/it_manifest_filter/it_manifest_filter.go
  function main (line 16) | func main() {

FILE: testing/it_manifest_filter/pkg/filter.go
  function ReplacePDWithEmptyDirs (line 21) | func ReplacePDWithEmptyDirs(in io.Reader, out io.Writer) error {
  function isEmptyYamlError (line 85) | func isEmptyYamlError(err error) bool {
  function findAndReplacePVC (line 96) | func findAndReplacePVC(obj map[string]interface{}) {
  function findPVC (line 108) | func findPVC(obj map[string]interface{}) {
  function processStatefulSet (line 125) | func processStatefulSet(obj *appsv1.StatefulSet) {
  function findAndReplaceIssuerName (line 154) | func findAndReplaceIssuerName(obj map[string]interface{}) {

FILE: testing/it_manifest_filter/pkg/filter_test.go
  function TestHappyPath (line 15) | func TestHappyPath(t *testing.T) {

FILE: testing/it_sidecar/client/sidecar_client.go
  type K8STestSetup (line 18) | type K8STestSetup struct
    method TestMain (line 42) | func (s *K8STestSetup) TestMain(m *testing.M) {
    method GetServiceLocalPort (line 70) | func (s *K8STestSetup) GetServiceLocalPort(serviceName string) int {
    method before (line 74) | func (s *K8STestSetup) before(wg *sync.WaitGroup) {
  type Callback (line 35) | type Callback

FILE: testing/it_sidecar/client/test_callback/sidecar_client_test.go
  function TestMain (line 24) | func TestMain(m *testing.M) {
  function TestReadyCallback (line 40) | func TestReadyCallback(t *testing.T) {

FILE: testing/it_sidecar/client/test_no_callback/sidecar_client_test_no_callback.go
  function TestMain (line 24) | func TestMain(m *testing.M) {

FILE: testing/it_sidecar/it_sidecar.go
  type portForwardConf (line 36) | type portForwardConf struct
    method String (line 40) | func (i *portForwardConf) String() string {
    method Set (line 44) | func (i *portForwardConf) Set(value string) error {
  type arrayFlags (line 57) | type arrayFlags
    method String (line 59) | func (i *arrayFlags) String() string {
    method Set (line 63) | func (i *arrayFlags) Set(value string) error {
  function init (line 78) | func init() {
  function contains (line 85) | func contains(v []string, item string) bool {
  function listReadyApps (line 96) | func listReadyApps(list []interface{}) (readypods, notReady []string) {
  function waitForPods (line 128) | func waitForPods(ctx context.Context, clientset *kubernetes.Clientset) e...
  function listReadyServices (line 170) | func listReadyServices(list []interface{}) (ready, notReady []string) {
  function waitForEndpoints (line 191) | func waitForEndpoints(ctx context.Context, clientset *kubernetes.Clients...
  function portForward (line 243) | func portForward(ctx context.Context, clientset *kubernetes.Clientset, c...
  function cleanup (line 300) | func cleanup(clientset *kubernetes.Clientset) {
  function main (line 311) | func main() {

FILE: testing/it_sidecar/stern/container_state.go
  type ContainerState (line 23) | type ContainerState
    method Match (line 43) | func (stateConfig ContainerState) Match(containerState v1.ContainerSta...
  constant RUNNING (line 26) | RUNNING    = "running"
  constant WAITING (line 27) | WAITING    = "waiting"
  constant TERMINATED (line 28) | TERMINATED = "terminated"
  function NewContainerState (line 31) | func NewContainerState(stateConfig string) (ContainerState, error) {

FILE: testing/it_sidecar/stern/main.go
  function Run (line 27) | func Run(ctx context.Context, namespace string, clientset *kubernetes.Cl...

FILE: testing/it_sidecar/stern/tail.go
  type Tail (line 29) | type Tail struct
    method Start (line 48) | func (t *Tail) Start(ctx context.Context, i v1.PodInterface) {
    method Close (line 91) | func (t *Tail) Close() {
    method Print (line 97) | func (t *Tail) Print(msg string) {
  function NewTail (line 38) | func NewTail(namespace, podName, containerName string) *Tail {

FILE: testing/it_sidecar/stern/watch.go
  type Target (line 30) | type Target struct
    method GetID (line 37) | func (t *Target) GetID() string {
  function Watch (line 44) | func Watch(ctx context.Context, i v1.PodInterface, podFilter *regexp.Reg...
Condensed preview — 251 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (517K chars).
[
  {
    "path": ".bazelignore",
    "chars": 16,
    "preview": "examples/\n.git/\n"
  },
  {
    "path": ".bazelrc",
    "chars": 2447,
    "preview": "########################\n# Import bazelrc presets\nimport %workspace%/tools/preset.bazelrc\n\n# Don’t want to push a rules "
  },
  {
    "path": ".bazelversion",
    "chars": 6,
    "preview": "7.6.0\n"
  },
  {
    "path": ".bcr/metadata.template.json",
    "chars": 537,
    "preview": "{\n    \"homepage\": \"https://github.com/adobe/rules_gitops\",\n    \"maintainers\": [\n        {\n            \"name\": \"Nick Scha"
  },
  {
    "path": ".bcr/presubmit.yml",
    "chars": 406,
    "preview": "bcr_test_module:\n    module_path: '.'\n    matrix:\n        bazel: ['7.x', '8.x', '9.x']\n        platform: ['debian10', 'm"
  },
  {
    "path": ".bcr/source.template.json",
    "chars": 277,
    "preview": "{\n    \"integrity\": \"**leave this alone**\",\n    \"strip_prefix\": \"{REPO}-{VERSION}\",\n    \"docs_url\": \"https://github.com/{"
  },
  {
    "path": ".gitattributes",
    "chars": 324,
    "preview": "#################################\n# Configuration for 'git archive'\n# See https://git-scm.com/docs/git-archive#ATTRIBUTE"
  },
  {
    "path": ".github/CODEOWNERS",
    "chars": 75,
    "preview": "#\n# The default owners for everything in the repo.\n#\n* @adobe/rules_gitops\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "chars": 2073,
    "preview": "# Contributing\n\nThanks for choosing to contribute!\n\nThe following are a set of guidelines to follow when contributing to"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "chars": 1278,
    "preview": "<!--- STOP! Before you open an issue please search this repository's issues to see if it has already been reported. This"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 1690,
    "preview": "<!--- Provide a general summary of your changes in the Title above -->\n\n## Description\n\n<!--- Describe your changes in d"
  },
  {
    "path": ".github/workflows/ci.yaml",
    "chars": 1415,
    "preview": "name: CI\n\non:\n  push:\n    branches:\n      - main\n      - feature/*\n  pull_request:\n    branches: [main]\n\njobs:\n  build:\n"
  },
  {
    "path": ".github/workflows/conventional-commits.yaml",
    "chars": 565,
    "preview": "# This helps the tag.yaml action to automatically create new releases\n#\n# tag.yaml requires all commits to follow the co"
  },
  {
    "path": ".github/workflows/publish.yaml",
    "chars": 1137,
    "preview": "# Publish new releases to Bazel Central Registry.\nname: Publish to BCR\non:\n  # Run the publish workflow after a successf"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 898,
    "preview": "# Cut a release whenever a new tag is pushed to the repo.\nname: Release\non:\n  # Can be triggered from the tag.yaml workf"
  },
  {
    "path": ".github/workflows/release_prep.sh",
    "chars": 1409,
    "preview": "#!/usr/bin/env bash\n\nset -o errexit -o nounset -o pipefail\n\n# Argument provided by reusable workflow caller, see\n# https"
  },
  {
    "path": ".gitignore",
    "chars": 65,
    "preview": ".vscode/\n.idea/\n.ijwb/\n.DS_Store\nThumbs.db\nbazel-*\n.bazelrc.user\n"
  },
  {
    "path": "BUILD.bazel",
    "chars": 1935,
    "preview": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "chars": 3237,
    "preview": "# Adobe Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributo"
  },
  {
    "path": "COPYRIGHT",
    "chars": 170,
    "preview": "© Copyright 2015-2026 Adobe. All rights reserved.\n\nAdobe holds the copyright for all the files found in this repository."
  },
  {
    "path": "LICENSE",
    "chars": 11620,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "MODULE.bazel",
    "chars": 2024,
    "preview": "\"adobe/rules_gitops\"\n\nmodule(\n    name = \"rules_gitops\",\n    version = \"\",\n    compatibility_level = 0,\n)\n\nbazel_dep(nam"
  },
  {
    "path": "README.md",
    "chars": 18242,
    "preview": "# Bazel GitOps Rules\n\n![CI](https://github.com/adobe/rules_gitops/workflows/CI/badge.svg?branch=main&event=push)\n\nBazel "
  },
  {
    "path": "WORKSPACE.bazel",
    "chars": 53,
    "preview": "# Marker that this is the root of a Bazel workspace.\n"
  },
  {
    "path": "adapters/BUILD.bazel",
    "chars": 504,
    "preview": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\n\nbzl_library(\n    name = \"providers\",\n    srcs = [\"providers.bzl\"],\n"
  },
  {
    "path": "adapters/external_image.bzl",
    "chars": 1381,
    "preview": "\"\"\"\nImplementation of external image information provider suitable for injection into manifests\n\"\"\"\n\nload(\":providers.bz"
  },
  {
    "path": "adapters/providers.bzl",
    "chars": 684,
    "preview": "\"\"\"Provider definitions for container image adapters.\"\"\"\n\nK8sPushInfo = provider(\n    doc = \"Information required to inj"
  },
  {
    "path": "adapters/rules_img.bzl",
    "chars": 1028,
    "preview": "\"\"\"Adapter for rules_img container images.\"\"\"\n\nload(\"@rules_img//img:providers.bzl\", \"DeployInfo\", \"ImageIndexInfo\", \"Im"
  },
  {
    "path": "e2e/BUILD.bazel",
    "chars": 442,
    "preview": "load(\"//e2e/util:util.bzl\", \"e2e_test\", \"kubectl_cmd\")\n\nkubectl_cmd(\n    name = \"verify_application\",\n    args = [\"-n \\\""
  },
  {
    "path": "e2e/deployment/BUILD.bazel",
    "chars": 2059,
    "preview": "load(\"@rules_gitops//adapters:rules_img.bzl\", \"k8s_push_info\")\nload(\"@rules_gitops//gitops:defs.bzl\", \"k8s_deploy\")\nload"
  },
  {
    "path": "e2e/deployment/helloworld.go",
    "chars": 1261,
    "preview": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"L"
  },
  {
    "path": "e2e/deployment/manifests/deployment.yaml",
    "chars": 1101,
    "preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: helloworld\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n     "
  },
  {
    "path": "e2e/deployment/manifests/service.yaml",
    "chars": 189,
    "preview": "apiVersion: v1\nkind: Service\nmetadata:\n  name: helloworld\n  labels:\n    app: helloworld\nspec:\n  ports:\n    - port: 80\n  "
  },
  {
    "path": "e2e/util/BUILD.bazel",
    "chars": 1259,
    "preview": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\nload(\"@rules_shell//shell:sh_binary.bzl\", \"sh_binary\")\n\npackage(\n   "
  },
  {
    "path": "e2e/util/kubectl_cmd.sh.tpl",
    "chars": 900,
    "preview": "#!/usr/bin/env bash\nset -o errexit\n\n# --- begin runfiles.bash initialization v3 ---\n# Copy-pasted from the Bazel Bash ru"
  },
  {
    "path": "e2e/util/setup.sh",
    "chars": 2887,
    "preview": "#!/usr/bin/env bash\nset -o errexit\n\n# --- begin runfiles.bash initialization v3 ---\n# Copy-pasted from the Bazel Bash ru"
  },
  {
    "path": "e2e/util/teardown.sh",
    "chars": 2475,
    "preview": "#!/usr/bin/env bash\nset -o errexit\n\n# --- begin runfiles.bash initialization v3 ---\n# Copy-pasted from the Bazel Bash ru"
  },
  {
    "path": "e2e/util/test.sh",
    "chars": 2319,
    "preview": "#!/usr/bin/env bash\n\n# --- begin runfiles.bash initialization v3 ---\n# Copy-pasted from the Bazel Bash runfiles library "
  },
  {
    "path": "e2e/util/util.bzl",
    "chars": 1984,
    "preview": "\"\"\"Utility macros for e2e testing with kind clusters.\"\"\"\n\nload(\"@bazel_lib//lib:expand_template.bzl\", \"expand_template\")"
  },
  {
    "path": "examples/README.md",
    "chars": 320,
    "preview": "# Bazel GitOps Rules Examples\n\n## Overview\n\nExamples projects to demonstrate Bazel GitOps Rules in use:\n\n- [helloworld]("
  },
  {
    "path": "examples/helloworld/.bazelrc",
    "chars": 26,
    "preview": "common --lockfile_mode=off"
  },
  {
    "path": "examples/helloworld/.bazelversion",
    "chars": 6,
    "preview": "7.6.0\n"
  },
  {
    "path": "examples/helloworld/BUILD.bazel",
    "chars": 5544,
    "preview": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "examples/helloworld/MODULE.bazel",
    "chars": 766,
    "preview": "bazel_dep(name = \"platforms\", version = \"1.0.0\")\n\nbazel_dep(name = \"rules_gitops\", dev_dependency = True)\n\nkustomize = u"
  },
  {
    "path": "examples/helloworld/deployment.yaml",
    "chars": 1087,
    "preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: helloworld\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n     "
  },
  {
    "path": "examples/helloworld/helloworld.go",
    "chars": 1261,
    "preview": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"L"
  },
  {
    "path": "examples/helloworld/helloworld_test.go",
    "chars": 1080,
    "preview": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"L"
  },
  {
    "path": "examples/helloworld/k8s_deploy_test.sh",
    "chars": 1853,
    "preview": "#!/usr/bin/env bash\n\n# Debug\n# set -x\n# RUNFILES_LIB_DEBUG=1\n\n# --- begin runfiles.bash initialization v2 ---\n# Copy-pas"
  },
  {
    "path": "examples/helloworld/service.yaml",
    "chars": 189,
    "preview": "apiVersion: v1\nkind: Service\nmetadata:\n  name: helloworld\n  labels:\n    app: helloworld\nspec:\n  ports:\n    - port: 80\n  "
  },
  {
    "path": "examples/legacy_docker/.bazelrc",
    "chars": 84,
    "preview": "common --lockfile_mode=off\ncommon --@io_bazel_rules_docker//transitions:enable=false"
  },
  {
    "path": "examples/legacy_docker/.bazelversion",
    "chars": 6,
    "preview": "7.6.0\n"
  },
  {
    "path": "examples/legacy_docker/BUILD.bazel",
    "chars": 1349,
    "preview": "load(\n    \"@io_bazel_rules_docker//container:container.bzl\",\n    \"container_image\",\n    \"container_push\",\n)\nload(\n    \"@"
  },
  {
    "path": "examples/legacy_docker/MODULE.bazel",
    "chars": 257,
    "preview": "bazel_dep(name = \"rules_gitops\", dev_dependency = True)\n\nkustomize = use_extension(\"@rules_gitops//kustomize:defs.bzl\", "
  },
  {
    "path": "examples/legacy_docker/WORKSPACE",
    "chars": 2325,
    "preview": "workspace(\n    name = \"hzrepo\",\n)\n\nload(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_archive\")\n\n###############"
  },
  {
    "path": "examples/legacy_docker/adapters/BUILD.bazel",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "examples/legacy_docker/adapters/docker.bzl",
    "chars": 955,
    "preview": "\"\"\"Adapter for legacy io_bazel_rules_docker container images.\"\"\"\n\nload(\"@io_bazel_rules_docker//container:providers.bzl\""
  },
  {
    "path": "examples/legacy_docker/container_contents.txt",
    "chars": 19,
    "preview": "Hello rules_docker!"
  },
  {
    "path": "examples/legacy_docker/deployment.yaml",
    "chars": 289,
    "preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: legacydocker\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n   "
  },
  {
    "path": "examples/legacy_docker/patches/docker.patch",
    "chars": 876,
    "preview": "diff --git a/container/image.bzl b/container/image.bzl\nindex ceeea01..f2762d4 100644\n--- a/container/image.bzl\n+++ b/con"
  },
  {
    "path": "gitops/BUILD.bazel",
    "chars": 1179,
    "preview": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\n\n# Copyright 2026 Adobe. All rights reserved.\n# This file is license"
  },
  {
    "path": "gitops/analysis/BUILD.bazel",
    "chars": 800,
    "preview": "load(\"@protobuf//bazel:proto_library.bzl\", \"proto_library\")\nload(\"@rules_go//go:def.bzl\", \"go_library\")\nload(\"@rules_go/"
  },
  {
    "path": "gitops/analysis/analysis.go",
    "chars": 16,
    "preview": "package analysis"
  },
  {
    "path": "gitops/analysis/analysis.proto",
    "chars": 6560,
    "preview": "// Copyright 2018 The Bazel Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "gitops/bazel/BUILD.bazel",
    "chars": 996,
    "preview": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "gitops/bazel/bazeltargets.go",
    "chars": 924,
    "preview": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"L"
  },
  {
    "path": "gitops/bazel/bazeltargets_test.go",
    "chars": 883,
    "preview": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"L"
  },
  {
    "path": "gitops/blaze_query/BUILD.bazel",
    "chars": 1317,
    "preview": "load(\"@protobuf//bazel:proto_library.bzl\", \"proto_library\")\nload(\"@rules_go//proto:def.bzl\", \"go_proto_library\")\n\n# Copy"
  },
  {
    "path": "gitops/blaze_query/build.proto",
    "chars": 18810,
    "preview": "// Copyright 2014 The Bazel Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"Lic"
  },
  {
    "path": "gitops/commitmsg/BUILD.bazel",
    "chars": 993,
    "preview": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "gitops/commitmsg/commitmsg.go",
    "chars": 1757,
    "preview": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"L"
  },
  {
    "path": "gitops/commitmsg/commitmsg_test.go",
    "chars": 1270,
    "preview": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"L"
  },
  {
    "path": "gitops/defs.bzl",
    "chars": 810,
    "preview": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "gitops/digester/BUILD.bazel",
    "chars": 868,
    "preview": "# Copyright 2024 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "gitops/digester/digester.go",
    "chars": 1990,
    "preview": "/*\nCopyright 2024 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"L"
  },
  {
    "path": "gitops/exec/BUILD",
    "chars": 860,
    "preview": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "gitops/exec/BUILD.bazel",
    "chars": 860,
    "preview": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "gitops/exec/exec.go",
    "chars": 1254,
    "preview": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"L"
  },
  {
    "path": "gitops/git/BUILD.bazel",
    "chars": 943,
    "preview": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "gitops/git/bitbucket/BUILD.bazel",
    "chars": 1032,
    "preview": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "gitops/git/bitbucket/bitbucket.go",
    "chars": 3578,
    "preview": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"L"
  },
  {
    "path": "gitops/git/bitbucket/bitbucket_test.go",
    "chars": 1982,
    "preview": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"L"
  },
  {
    "path": "gitops/git/bitbucket/testdata/create_pr.json",
    "chars": 718,
    "preview": "{\n    \"title\": \"Deploy to test 1\",\n    \"description\": \"Deploy to test 1.\",\n    \"state\": \"OPEN\",\n    \"open\": true,\n    \"c"
  },
  {
    "path": "gitops/git/git.go",
    "chars": 5702,
    "preview": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"L"
  },
  {
    "path": "gitops/git/github/BUILD.bazel",
    "chars": 372,
    "preview": "load(\"@rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"github.go\"],\n    "
  },
  {
    "path": "gitops/git/github/github.go",
    "chars": 2323,
    "preview": "package github\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"flag\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\n\t\"github.com/google/go-gith"
  },
  {
    "path": "gitops/git/gitlab/BUILD.bazel",
    "chars": 414,
    "preview": "load(\"@rules_go//go:def.bzl\", \"go_library\", \"go_test\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"gitlab"
  },
  {
    "path": "gitops/git/gitlab/gitlab.go",
    "chars": 1704,
    "preview": "package gitlab\n\nimport (\n\t\"errors\"\n\t\"flag\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\n\t\"github.com/xanzy/go-gitlab\"\n)\n\nvar ("
  },
  {
    "path": "gitops/git/gitlab/gitlab_test.go",
    "chars": 1223,
    "preview": "package gitlab\n\nimport \"testing\"\n\nfunc TestCreatePRRemote(t *testing.T) {\n\tt.Skip(\"Manual\")\n\tvar (\n\t\ttestGitlabToken = \""
  },
  {
    "path": "gitops/git/server.go",
    "chars": 282,
    "preview": "package git\n\ntype Server interface {\n\tCreatePR(from, to, title, body string) error\n}\n\ntype ServerFunc func(from, to, tit"
  },
  {
    "path": "gitops/prer/BUILD.bazel",
    "chars": 1583,
    "preview": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "gitops/prer/create_gitops_prs.go",
    "chars": 8316,
    "preview": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"L"
  },
  {
    "path": "gitops/private/BUILD.bazel",
    "chars": 569,
    "preview": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\n\npackage(\n    default_visibility = [\"//gitops:__subpackages__\"],\n)\n\n"
  },
  {
    "path": "gitops/private/gitops.bzl",
    "chars": 4986,
    "preview": "\"\"\"GitOps rule for generating deployment manifests.\"\"\"\n\nload(\"//adapters:providers.bzl\", \"K8sPushInfo\")\nload(\"//kustomiz"
  },
  {
    "path": "gitops/private/k8s_deploy.bzl",
    "chars": 9922,
    "preview": "\"\"\"Macro for creating Kubernetes deployment targets with GitOps support.\"\"\"\n\nload(\"//gitops/private:gitops.bzl\", _gitops"
  },
  {
    "path": "gitops/private/k8s_gitops.sh.tpl",
    "chars": 2528,
    "preview": "#!/usr/bin/env bash\n# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License"
  },
  {
    "path": "gitops/private/nameprefix_deployment_labels_config.yaml",
    "chars": 415,
    "preview": "\nnamePrefix:\n- path: metadata/name\n- path: spec/selector/matchLabels/app\n  kind: Deployment\n- path: spec/template/metada"
  },
  {
    "path": "gitops/private/namesuffix_deployment_labels_config.yaml",
    "chars": 441,
    "preview": "\nnameSuffix:\n  - path: metadata/name\n  - path: spec/selector/matchLabels/app\n    kind: Deployment\n  - path: spec/templat"
  },
  {
    "path": "gitops/private/test/BUILD.bazel",
    "chars": 1945,
    "preview": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\n\n# Copyright 2026 Adobe. All rights reserved.\n# This file is license"
  },
  {
    "path": "gitops/private/test/deployment.yaml",
    "chars": 270,
    "preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp\nspec:\n  selector:\n    matchLabels:\n      app: myapp\n  templ"
  },
  {
    "path": "gitops/private/test/deployment1.yaml",
    "chars": 272,
    "preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp\nspec:\n  selector:\n    matchLabels:\n      app: myapp\n  templ"
  },
  {
    "path": "gitops/private/test/goldens/deploy_test.golden",
    "chars": 390,
    "preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp\n  namespace: ci\nspec:\n  selector:\n    matchLabels:\n      ap"
  },
  {
    "path": "gitops/private/test/goldens/external_image_test.golden",
    "chars": 288,
    "preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp\n  namespace: ci\nspec:\n  selector:\n    matchLabels:\n      ap"
  },
  {
    "path": "gitops/private/test/images/BUILD.bazel",
    "chars": 1275,
    "preview": "load(\"@rules_gitops//adapters:rules_img.bzl\", \"k8s_push_info\")\nload(\"@rules_img//img:image.bzl\", \"image_manifest\")\nload("
  },
  {
    "path": "gitops/private/test/images/container_content.txt",
    "chars": 40,
    "preview": "this file goes into the test container\n\n"
  },
  {
    "path": "gitops/private/test/templates.bzl",
    "chars": 8547,
    "preview": "# Copyright 2017 The Bazel Authors. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"Licens"
  },
  {
    "path": "go.mod",
    "chars": 2703,
    "preview": "module github.com/adobe/rules_gitops\n\ngo 1.24.12\n\nrequire (\n\tgithub.com/ghodss/yaml v1.0.0\n\tgithub.com/google/go-cmp v0."
  },
  {
    "path": "go.sum",
    "chars": 29037,
    "preview": "al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho=\nal.essio.dev/pkg/shellescape v1.5.1/"
  },
  {
    "path": "kubectl/BUILD.bazel",
    "chars": 572,
    "preview": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\nload(\":defs.bzl\", \"resolved_toolchain\")\n\ntoolchain_type(\n    name = "
  },
  {
    "path": "kubectl/defs.bzl",
    "chars": 488,
    "preview": "\"\"\"Public API for kubectl rules.\"\"\"\n\nload(\"//kubectl/private:extension.bzl\", _kubectl = \"kubectl\")\nload(\"//kubectl/priva"
  },
  {
    "path": "kubectl/private/BUILD.bazel",
    "chars": 1066,
    "preview": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\n\npackage(\n    default_visibility = [\"//kubectl:__subpackages__\"],\n)\n"
  },
  {
    "path": "kubectl/private/extension.bzl",
    "chars": 2717,
    "preview": "\"\"\"Module extension for kubectl toolchain.\"\"\"\n\nload(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_file\")\nload(\"/"
  },
  {
    "path": "kubectl/private/kubeconfig.bzl",
    "chars": 5153,
    "preview": "\"\"\"Repository rule for configuring kubectl and kubeconfig.\"\"\"\n\ndef _kubectl_config(repository_ctx, args):\n    kubectl = "
  },
  {
    "path": "kubectl/private/kubectl_binary.bzl",
    "chars": 4663,
    "preview": "\"\"\"\nSimple rule for running kubectl from the toolchain config\n\"\"\"\n\nload(\"//adapters:providers.bzl\", \"K8sPushInfo\")\nload("
  },
  {
    "path": "kubectl/private/platforms.bzl",
    "chars": 656,
    "preview": "\"\"\"Platform definitions for kubectl toolchain.\"\"\"\n\nPLATFORMS = {\n    \"darwin_amd64\": struct(\n        compatible_with = ["
  },
  {
    "path": "kubectl/private/providers.bzl",
    "chars": 332,
    "preview": "\"\"\"Provider definitions for kubectl toolchain.\"\"\"\n\nKubectlToolchainInfo = provider(\n    doc = \"Toolchain information abo"
  },
  {
    "path": "kubectl/private/resolved_toolchain.bzl",
    "chars": 814,
    "preview": "\"\"\"This module implements an alias rule to the resolved toolchain.\n\"\"\"\n\nDOC = \"\"\"\\\nExposes a concrete toolchain which is"
  },
  {
    "path": "kubectl/private/run-all.sh.tpl",
    "chars": 1074,
    "preview": "#!/usr/bin/env bash\n\nset -eu\n\n# --- begin runfiles.bash initialization v3 ---\n# Copy-pasted from the Bazel Bash runfiles"
  },
  {
    "path": "kubectl/private/toolchain.bzl",
    "chars": 1405,
    "preview": "\"Kubectl toolchain rule\"\n\nload(\"//kubectl/private:providers.bzl\", \"KubectlToolchainInfo\")\n\n# Avoid using non-normalized "
  },
  {
    "path": "kubectl/private/versions/BUILD.bazel",
    "chars": 549,
    "preview": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\nload(\"@rules_go//go:def.bzl\", \"go_binary\", \"go_library\")\n\ngo_library"
  },
  {
    "path": "kubectl/private/versions/update_versions.go",
    "chars": 8803,
    "preview": "// update_versions downloads kubectl release information from dl.k8s.io\n// and generates a versions.bzl file with SHA256"
  },
  {
    "path": "kubectl/private/versions/versions.bzl",
    "chars": 6258,
    "preview": "\"\"\"Generated by update_versions.go - do not edit manually.\"\"\"\n\nLATEST_KUBECTL_VERSION = \"1.35.0\"\n\nVERSIONS = {\n    \"1.35"
  },
  {
    "path": "kustomize/BUILD.bazel",
    "chars": 646,
    "preview": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\nload(\"//kustomize/private:resolved_toolchain.bzl\", \"resolved_toolcha"
  },
  {
    "path": "kustomize/defs.bzl",
    "chars": 672,
    "preview": "\"\"\"Public API for kustomize rules.\"\"\"\n\nload(\"//kustomize/private:extension.bzl\", _kustomize = \"kustomize\")\nload(\"//kusto"
  },
  {
    "path": "kustomize/private/BUILD.bazel",
    "chars": 1092,
    "preview": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\n\npackage(\n    default_visibility = [\"//kustomize:__subpackages__\"],\n"
  },
  {
    "path": "kustomize/private/extension.bzl",
    "chars": 2759,
    "preview": "\"\"\"Module extension for kustomize toolchain.\"\"\"\n\nload(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_archive\")\nlo"
  },
  {
    "path": "kustomize/private/kustomization.bzl",
    "chars": 14349,
    "preview": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "kustomize/private/kustomize_binary.bzl",
    "chars": 705,
    "preview": "\"\"\"\nSimple rule for running kustomize from the toolchain config\n\"\"\"\n\ndef _kustomize_binary(ctx):\n    executable = ctx.to"
  },
  {
    "path": "kustomize/private/platforms.bzl",
    "chars": 658,
    "preview": "\"\"\"Platform definitions for kustomize toolchain.\"\"\"\n\nPLATFORMS = {\n    \"darwin_amd64\": struct(\n        compatible_with ="
  },
  {
    "path": "kustomize/private/providers.bzl",
    "chars": 545,
    "preview": "\"\"\"Provider definitions for kustomize rules.\"\"\"\n\nKustomizeToolchainInfo = provider(\n    doc = \"Toolchain information abo"
  },
  {
    "path": "kustomize/private/resolved_toolchain.bzl",
    "chars": 820,
    "preview": "\"\"\"This module implements an alias rule to the resolved toolchain.\n\"\"\"\n\nDOC = \"\"\"\\\nExposes a concrete toolchain which is"
  },
  {
    "path": "kustomize/private/show.bzl",
    "chars": 1598,
    "preview": "\"\"\"Rule for displaying rendered kustomize output.\"\"\"\n\ndef _show_impl(ctx):\n    script_content = \"#!/usr/bin/env bash\\nse"
  },
  {
    "path": "kustomize/private/tests/BUILD.bazel",
    "chars": 478,
    "preview": "load(\"//kustomize/private:kustomization.bzl\", \"kustomization\")\nload(\"//tools:util.bzl\", \"golden_test\")\n\n# Verify that ru"
  },
  {
    "path": "kustomize/private/tests/crb.yaml",
    "chars": 188,
    "preview": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: crb-name\nsubjects:\n- kind: Group\n  n"
  },
  {
    "path": "kustomize/private/tests/deployment.yaml",
    "chars": 262,
    "preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp\nspec:\n  selector:\n    matchLabels:\n      app: myapp\n  templ"
  },
  {
    "path": "kustomize/private/tests/goldens/raw.golden",
    "chars": 601,
    "preview": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: crb-name\nroleRef:\n  kind: ClusterRol"
  },
  {
    "path": "kustomize/private/tests/service.yaml",
    "chars": 143,
    "preview": "apiVersion: v1\nkind: Service\nmetadata:\n  name: myapp\nspec:\n  ports:\n  - port: 80\n    name: web\n    targetPort: 8080\n  se"
  },
  {
    "path": "kustomize/private/toolchain.bzl",
    "chars": 1427,
    "preview": "\"Kustomize toolchain rule\"\n\nload(\"//kustomize/private:providers.bzl\", \"KustomizeToolchainInfo\")\n\n# Avoid using non-norma"
  },
  {
    "path": "kustomize/private/versions/BUILD.bazel",
    "chars": 553,
    "preview": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\nload(\"@rules_go//go:def.bzl\", \"go_binary\", \"go_library\")\n\ngo_library"
  },
  {
    "path": "kustomize/private/versions/update_versions.go",
    "chars": 6872,
    "preview": "// update_versions downloads kustomize release information from GitHub\n// and generates a versions.bzl file with SHA256 "
  },
  {
    "path": "kustomize/private/versions/versions.bzl",
    "chars": 1830,
    "preview": "\"\"\"Generated by update_versions.go - do not edit manually.\"\"\"\n\nLATEST_KUSTOMIZE_VERSION = \"5.8.0\"\n\nVERSIONS = {\n    \"5.8"
  },
  {
    "path": "renovate.json",
    "chars": 373,
    "preview": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \":dependencyDashboard\",\n    \":ena"
  },
  {
    "path": "resolver/BUILD.bazel",
    "chars": 401,
    "preview": "load(\"@rules_go//go:def.bzl\", \"go_binary\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"reso"
  },
  {
    "path": "resolver/pkg/BUILD.bazel",
    "chars": 650,
    "preview": "load(\"@rules_go//go:def.bzl\", \"go_library\", \"go_test\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"resolv"
  },
  {
    "path": "resolver/pkg/resolver.go",
    "chars": 4615,
    "preview": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"L"
  },
  {
    "path": "resolver/pkg/resolver_test.go",
    "chars": 2312,
    "preview": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"L"
  },
  {
    "path": "resolver/pkg/testdata/cwf.expected.yaml",
    "chars": 229,
    "preview": "apiVersion: apps/v1\nkind: CronWorkFlow\nmetadata:\n  name: aaa\n  namespace: stats-dev\nspec:\n  workflowSpec:\n    metadata:\n"
  },
  {
    "path": "resolver/pkg/testdata/cwf.yaml",
    "chars": 215,
    "preview": "apiVersion: apps/v1\nkind: CronWorkFlow\nmetadata:\n  name: aaa\n  namespace: stats-dev\nspec:\n  workflowSpec:\n    metadata:\n"
  },
  {
    "path": "resolver/pkg/testdata/digest.expected.yaml",
    "chars": 210,
    "preview": "deployment-templates:\n  deployment:\n    containers:\n      hello:\n        image:\n          digest: sha256:{{@@//hello-wor"
  },
  {
    "path": "resolver/pkg/testdata/digest.yaml",
    "chars": 212,
    "preview": "deployment-templates:\n  deployment:\n    containers:\n      hello:\n        image:\n          digest: \"sha256:{{@@//hello-wo"
  },
  {
    "path": "resolver/pkg/testdata/emptyinit.expected.yaml",
    "chars": 256,
    "preview": "apiVersion: apps/v1\nkind: CronWorkFlow\nmetadata:\n  name: aaa\n  namespace: stats-dev\nspec:\n  workflowSpec:\n    metadata:\n"
  },
  {
    "path": "resolver/pkg/testdata/emptyinit.yaml",
    "chars": 237,
    "preview": "apiVersion: apps/v1\nkind: CronWorkFlow\nmetadata:\n  name: aaa\n  namespace: stats-dev\nspec:\n  workflowSpec:\n    metadata:\n"
  },
  {
    "path": "resolver/pkg/testdata/flinkapp.expected.yaml",
    "chars": 147,
    "preview": "apiVersion: flink.k8s.io/v1beta1\nkind: FlinkApplication\nmetadata:\n  name: wordcount-operator-example\nspec:\n  image: dock"
  },
  {
    "path": "resolver/pkg/testdata/flinkapp.yaml",
    "chars": 131,
    "preview": "apiVersion: flink.k8s.io/v1beta1\nkind: FlinkApplication\nmetadata:\n  name: wordcount-operator-example\nspec:\n  image: flin"
  },
  {
    "path": "resolver/pkg/testdata/happypath.expected.yaml",
    "chars": 2845,
    "preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app: prometheus2\n  name: prometheus2\n  namespace: rtb-prod\n"
  },
  {
    "path": "resolver/pkg/testdata/happypath.yaml",
    "chars": 2718,
    "preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app: prometheus2\n  name: prometheus2\n  namespace: rtb-prod\n"
  },
  {
    "path": "resolver/pkg/testdata/zk.expected.yaml",
    "chars": 145,
    "preview": "apiVersion: zookeeper.pravega.io/v1beta1\nkind: ZookeeperCluster\nmetadata:\n  name: zk-cluster\nspec:\n  image:\n    reposito"
  },
  {
    "path": "resolver/pkg/testdata/zk.yaml",
    "chars": 145,
    "preview": "apiVersion: zookeeper.pravega.io/v1beta1\nkind: ZookeeperCluster\nmetadata:\n  name: zk-cluster\nspec:\n  image:\n    reposito"
  },
  {
    "path": "resolver/resolver.go",
    "chars": 1809,
    "preview": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"L"
  },
  {
    "path": "stamper/BUILD.bazel",
    "chars": 1475,
    "preview": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\n\n# Copyright 2026 Adobe. All rights reserved.\n# This file is license"
  },
  {
    "path": "stamper/main.go",
    "chars": 2475,
    "preview": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"L"
  },
  {
    "path": "stamper/stamp.bzl",
    "chars": 3885,
    "preview": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "templating/BUILD.bazel",
    "chars": 1062,
    "preview": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "templating/fasttemplate/BUILD.bazel",
    "chars": 1005,
    "preview": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "templating/fasttemplate/LICENSE",
    "chars": 1088,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2015 Aliaksandr Valialkin\n\nPermission is hereby granted, free of charge, to any per"
  },
  {
    "path": "templating/fasttemplate/README.md",
    "chars": 466,
    "preview": "fasttemplate\n============\n\nSimple and fast template engine for Go.\nForked from [fasttemplate](https://github.com/valyala"
  },
  {
    "path": "templating/fasttemplate/example_test.go",
    "chars": 2105,
    "preview": "package fasttemplate\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n)\n\nfunc ExampleTemplate() {\n\ttemplate := \"http://{{host}}/?foo={{"
  },
  {
    "path": "templating/fasttemplate/template.go",
    "chars": 4423,
    "preview": "// Package fasttemplate implements simple and fast template library.\n//\n// Fasttemplate is faster than text/template, st"
  },
  {
    "path": "templating/fasttemplate/template_test.go",
    "chars": 3582,
    "preview": "package fasttemplate\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n)\n\nfunc TestExecuteFunc(t *testing.T) {\n\ttestExecuteFunc(t, \"\","
  },
  {
    "path": "templating/main.go",
    "chars": 3950,
    "preview": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"L"
  },
  {
    "path": "templating/testdata/generated",
    "chars": 28,
    "preview": "generated data here, {{VAR}}"
  },
  {
    "path": "templating/testdata/stable-status.txt",
    "chars": 67,
    "preview": "BUILD_EMBED_LABEL \nBUILD_HOST pesterni-macOS-1\nBUILD_USER pesterni\n"
  },
  {
    "path": "templating/testdata/template1.tpl",
    "chars": 34,
    "preview": "Welcome, {{VAR}}!\n{{imports.IMP}}\n"
  },
  {
    "path": "templating/testdata/volatile-status.txt",
    "chars": 149,
    "preview": "BUILD_SCM_REVISION cc45ebf5a259abf555a73ba9f751d954c4d26612\nBUILD_SCM_SHORT_REVISION cc45ebf5a2\nBUILD_SCM_STATUS Modifie"
  },
  {
    "path": "testing/BUILD.bazel",
    "chars": 266,
    "preview": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\n\nbzl_library(\n    name = \"defs\",\n    srcs = [\"defs.bzl\"],\n    visibi"
  },
  {
    "path": "testing/defs.bzl",
    "chars": 297,
    "preview": "\"\"\"Public API for Kubernetes testing rules.\"\"\"\n\nload(\"//testing/private:k8s_test_namespace.bzl\", _k8s_test_namespace = \""
  },
  {
    "path": "testing/it_manifest_filter/BUILD.bazel",
    "chars": 457,
    "preview": "load(\"@rules_go//go:def.bzl\", \"go_binary\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"it_m"
  },
  {
    "path": "testing/it_manifest_filter/it_manifest_filter.go",
    "chars": 755,
    "preview": "package main\n\nimport (\n\t\"flag\"\n\t\"log\"\n\t\"os\"\n\n\tfilter \"github.com/adobe/rules_gitops/testing/it_manifest_filter/pkg\"\n)\n\nv"
  },
  {
    "path": "testing/it_manifest_filter/pkg/BUILD.bazel",
    "chars": 924,
    "preview": "load(\"@rules_go//go:def.bzl\", \"go_library\", \"go_test\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"filter"
  },
  {
    "path": "testing/it_manifest_filter/pkg/filter.go",
    "chars": 4450,
    "preview": "package filter\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"strings\"\n\n\tyamlenc \"github.com/ghodss/yaml\"\n\tappsv1 \"k8s.io/api/apps/v1\"\n"
  },
  {
    "path": "testing/it_manifest_filter/pkg/filter_test.go",
    "chars": 1138,
    "preview": "package filter_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\tfilter \"github.com/adobe/rules_"
  },
  {
    "path": "testing/it_manifest_filter/pkg/testdata/certificate.expected.yaml",
    "chars": 575,
    "preview": "apiVersion: certmanager.k8s.io/v1alpha1\nkind: Certificate\nmetadata:\n  name: actv-notification-certificate\n  namespace: n"
  },
  {
    "path": "testing/it_manifest_filter/pkg/testdata/certificate.yaml",
    "chars": 584,
    "preview": "apiVersion: certmanager.k8s.io/v1alpha1\nkind: Certificate\nmetadata:\n  name: actv-notification-certificate\n  namespace: n"
  },
  {
    "path": "testing/it_manifest_filter/pkg/testdata/happypath.expected.yaml",
    "chars": 1324,
    "preview": "apiVersion: v1\nkind: Service\nmetadata:\n  name: mysql\n  namespace: pesterni\nspec:\n  ports:\n  - port: 3306\n  selector:\n   "
  },
  {
    "path": "testing/it_manifest_filter/pkg/testdata/happypath.yaml",
    "chars": 1622,
    "preview": "apiVersion: v1\nkind: Service\nmetadata:\n  name: mysql\n  namespace: pesterni\nspec:\n  ports:\n  - port: 3306\n  selector:\n   "
  },
  {
    "path": "testing/it_manifest_filter/pkg/testdata/statefulset.expected.yaml",
    "chars": 2327,
    "preview": "apiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  creationTimestamp: null\n  name: zk\n  namespace: actv-apps-uat\nspec:\n  "
  },
  {
    "path": "testing/it_manifest_filter/pkg/testdata/statefulset.yaml",
    "chars": 2780,
    "preview": "apiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: zk\n  namespace: actv-apps-uat\nspec:\n  podManagementPolicy: Paral"
  },
  {
    "path": "testing/it_manifest_filter/pkg/testdata/statefulset2.expected.yaml",
    "chars": 2371,
    "preview": "apiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  creationTimestamp: null\n  name: zk\n  namespace: actv-apps-uat\nspec:\n  "
  },
  {
    "path": "testing/it_manifest_filter/pkg/testdata/statefulset2.yaml",
    "chars": 3074,
    "preview": "apiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: zk\n  namespace: actv-apps-uat\nspec:\n  podManagementPolicy: Paral"
  },
  {
    "path": "testing/it_manifest_filter/pkg/testdata/statefulset3.expected.yaml",
    "chars": 2327,
    "preview": "apiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  creationTimestamp: null\n  name: zk\n  namespace: actv-apps-uat\nspec:\n  "
  },
  {
    "path": "testing/it_manifest_filter/pkg/testdata/statefulset3.yaml",
    "chars": 2718,
    "preview": "apiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: zk\n  namespace: actv-apps-uat\nspec:\n  podManagementPolicy: Paral"
  },
  {
    "path": "testing/it_sidecar/BUILD.bazel",
    "chars": 1130,
    "preview": "load(\"@rules_go//go:def.bzl\", \"go_binary\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"it_s"
  },
  {
    "path": "testing/it_sidecar/client/BUILD.bazel",
    "chars": 318,
    "preview": "load(\"@rules_go//go:def.bzl\", \"go_library\")\n\nexports_files([\"noop.setup\"])\n\ngo_library(\n    name = \"go_default_library\","
  },
  {
    "path": "testing/it_sidecar/client/noop.setup",
    "chars": 158,
    "preview": "#! /bin/bash\n\n# This is a no-op setup script that simulates the K8Setup script output\necho \"FORWARD foo:8000:8000\"\necho "
  },
  {
    "path": "testing/it_sidecar/client/sidecar_client.go",
    "chars": 3748,
    "preview": "package client\n\nimport (\n\t\"bufio\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n"
  },
  {
    "path": "testing/it_sidecar/client/test_callback/BUILD.bazel",
    "chars": 373,
    "preview": "load(\"@rules_go//go:def.bzl\", \"go_test\")\n\ngo_test(\n    name = \"go_default_test\",\n    srcs = [\"sidecar_client_test.go\"],\n"
  },
  {
    "path": "testing/it_sidecar/client/test_callback/sidecar_client_test.go",
    "chars": 1271,
    "preview": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"L"
  },
  {
    "path": "testing/it_sidecar/client/test_no_callback/BUILD.bazel",
    "chars": 341,
    "preview": "load(\"@rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"sidecar_client_te"
  },
  {
    "path": "testing/it_sidecar/client/test_no_callback/sidecar_client_test_no_callback.go",
    "chars": 942,
    "preview": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"L"
  },
  {
    "path": "testing/it_sidecar/it_sidecar.go",
    "chars": 10013,
    "preview": "package main\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/f"
  },
  {
    "path": "testing/it_sidecar/stern/BUILD.bazel",
    "chars": 770,
    "preview": "load(\"@rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\n        \"containe"
  }
]

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

About this extraction

This page contains the full source code of the adobe/rules_gitops GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 251 files (462.8 KB), approximately 138.1k tokens, and a symbol index with 164 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!