[
  {
    "path": ".bazelignore",
    "content": "examples/\n.git/\n"
  },
  {
    "path": ".bazelrc",
    "content": "########################\n# Import bazelrc presets\nimport %workspace%/tools/preset.bazelrc\n\n# Don’t want to push a rules author to update their deps if not needed.\n# https://bazel.build/reference/command-line-reference#flag--check_direct_dependencies\n# https://bazelbuild.slack.com/archives/C014RARENH0/p1691158021917459?thread_ts=1691156601.420349&cid=C014RARENH0\ncommon --config=ruleset\n\n# Bazel settings that apply to this repository.\n# Take care to document any settings that you expect users to apply.\n# Settings that apply only to CI are in .github/workflows/ci.bazelrc\n\n# Required until this is the default; expected in Bazel 7\ncommon --enable_bzlmod\n\n# C++17 is required by protobuf and abseil-cpp\nbuild --cxxopt=-std=c++17\nbuild --host_cxxopt=-std=c++17\n\n# Ensure that the MODULE.bazel.lock file is complete and committed.\n# This is an important security measure: it ensures that developers on the\n# same rule set download dependencies at the same versions with the same bits.\n# This setting does not affect modules that depend on this module.\n#\n# When updating dependencies, use --lockfile_mode=refresh, for example:\n#     bazel mod tidy --lockfile_mode=refresh\n#\n# When testing different versions of Bazel, use --lockfile_mode=update or\n# --lockfile_mode=off. The lock file format changes over time, and different\n# versions of Bazel may expect different syntax. Bazel also implicitly requires\n# some modules, and different versions have different dependencies, which\n# also affects the contents of the lock file.\ncommon --lockfile_mode=off\n\n# This directory is configured in GitHub actions to be persisted between runs.\n# We do not enable the repository cache to cache downloaded external artifacts\n# as these are generally faster to download again than to fetch them from the\n# GitHub actions cache.\ncommon:ci --disk_cache=~/.cache/bazel\n\ncommon --@protobuf//bazel/toolchains:prefer_prebuilt_protoc\n\n# Load any settings specific to the current user.\n# .bazelrc.user should appear in .gitignore so that settings are not shared with team members\n# This needs to be last statement in this\n# config, as the user configuration should be able to overwrite flags from this file.\n# See https://docs.bazel.build/versions/master/best-practices.html#bazelrc\n# (Note that we use .bazelrc.user so the file appears next to .bazelrc in directory listing,\n# rather than user.bazelrc as suggested in the Bazel docs)\ntry-import %workspace%/.bazelrc.user"
  },
  {
    "path": ".bazelversion",
    "content": "7.6.0\n"
  },
  {
    "path": ".bcr/metadata.template.json",
    "content": "{\n    \"homepage\": \"https://github.com/adobe/rules_gitops\",\n    \"maintainers\": [\n        {\n            \"name\": \"Nick Schaap\",\n            \"email\": \"schaap@adobe.com\",\n            \"github\": \"nickschaap\",\n            \"github_user_id\": 4684894\n        },\n        {\n            \"name\": \"Konstantin Zadorozhny\",\n            \"email\": \"zadorozh@adobe.com\",\n            \"github\": \"kzadorozhny\",\n            \"github_user_id\": 1616702\n        }\n    ],\n    \"repository\": [\"github:adobe/rules_gitops\"],\n    \"versions\": [],\n    \"yanked_versions\": {}\n}"
  },
  {
    "path": ".bcr/presubmit.yml",
    "content": "bcr_test_module:\n    module_path: '.'\n    matrix:\n        bazel: ['7.x', '8.x', '9.x']\n        platform: ['debian10', 'macos', 'ubuntu2004']\n    tasks:\n        run_tests:\n            name: 'Run test module'\n            bazel: ${{ bazel }}\n            platform: ${{ platform }}\n            test_flags:\n                - \"--test_tag_filters=-skip-bazel-ci\"\n            test_targets:\n                - '//...'"
  },
  {
    "path": ".bcr/source.template.json",
    "content": "{\n    \"integrity\": \"**leave this alone**\",\n    \"strip_prefix\": \"{REPO}-{VERSION}\",\n    \"docs_url\": \"https://github.com/{OWNER}/{REPO}/releases/download/{TAG}/{REPO}-{TAG}.docs.tar.gz\",\n    \"url\": \"https://github.com/{OWNER}/{REPO}/releases/download/{TAG}/{REPO}-{TAG}.tar.gz\"\n}"
  },
  {
    "path": ".gitattributes",
    "content": "#################################\n# Configuration for 'git archive'\n# See https://git-scm.com/docs/git-archive#ATTRIBUTES\n\n# Don't include examples in the distribution artifact, to reduce size.\n# You may want to add additional exclusions for folders or files that users don't need.\nexamples export-ignore\ntests export-ignore"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "#\n# The default owners for everything in the repo.\n#\n* @adobe/rules_gitops\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contributing\n\nThanks for choosing to contribute!\n\nThe following are a set of guidelines to follow when contributing to this project.\n\n## Code Of Conduct\n\nThis project adheres to the Adobe [code of conduct](../CODE_OF_CONDUCT.md). By participating,\nyou are expected to uphold this code. Please report unacceptable behavior to\n[Grp-opensourceoffice@adobe.com](mailto:Grp-opensourceoffice@adobe.com).\n\n## Have A Question?\n\nStart by filing an issue. The existing committers on this project work to reach\nconsensus around project direction and issue solutions within issue threads\n(when appropriate).\n\n## Contributor License Agreement\n\nAll third-party contributions to this project must be accompanied by a signed contributor\nlicense agreement. This gives Adobe permission to redistribute your contributions\nas part of the project. [Sign our CLA](https://opensource.adobe.com/cla.html). You\nonly need to submit an Adobe CLA one time, so if you have submitted one previously,\nyou are good to go!\n\n## Code Reviews\n\nAll submissions should come in the form of pull requests and need to be reviewed\nby project committers. Read [GitHub's pull request documentation](https://help.github.com/articles/about-pull-requests/)\nfor more information on sending pull requests.\n\nLastly, please follow the [pull request template](PULL_REQUEST_TEMPLATE.md) when\nsubmitting a pull request!\n\n## From Contributor To Committer\n\nWe love contributions from our community! If you'd like to go a step beyond contributor\nand become a committer with full write access and a say in the project, you must\nbe invited to the project. The existing committers employ an internal nomination\nprocess that must reach lazy consensus (silence is approval) before invitations\nare issued. If you feel you are qualified and want to get more deeply involved,\nfeel free to reach out to existing committers to have a conversation about that.\n\n## Security Issues\n\nSecurity issues shouldn't be reported on this issue tracker. Instead, [file an issue to our security experts](https://helpx.adobe.com/security/alertus.html).\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "<!--- 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. -->\n<!--- 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. -->\n\n### Description of the problem / feature request:\n\n> Replace this line with your answer.\n\n### Feature requests: what underlying problem are you trying to solve with this feature?\n\n> Replace this line with your answer.\n\n### Bugs: what's the simplest, easiest way to reproduce this bug? Please provide a minimal example if possible.\n\n> Replace this line with your answer.\n\n### What operating system are you running Bazel on?\n\n> Replace this line with your answer.\n\n### What's the output of `bazel info release`?\n\n> Replace this line with your answer.\n\n### If `bazel info release` returns \"development version\" or \"(@non-git)\", tell us how you built Bazel.\n\n> Replace this line with your answer.\n\n### Any other information, logs, or outputs that you want to share?\n\n> Replace these lines with your answer.\n>\n> If the files are large, upload as attachment or provide link.\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "<!--- Provide a general summary of your changes in the Title above -->\n\n## Description\n\n<!--- Describe your changes in detail -->\n\n## Related Issue\n\n<!--- This project only accepts pull requests related to open issues -->\n<!--- If suggesting a new feature or change, please discuss it in an issue first -->\n<!--- If fixing a bug, there should be an issue describing it with steps to reproduce -->\n<!--- Please link to the issue here: -->\n\n## Motivation and Context\n\n<!--- Why is this change required? What problem does it solve? -->\n\n## How Has This Been Tested?\n\n<!--- Please describe in detail how you tested your changes. -->\n<!--- Include details of your testing environment, and the tests you ran to -->\n<!--- see how your change affects other areas of the code, etc. -->\n\n## Types of changes\n\n<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->\n\n- [ ] Bug fix (non-breaking change which fixes an issue)\n- [ ] New feature (non-breaking change which adds functionality)\n- [ ] Breaking change (fix or feature that would cause existing functionality to change)\n\n## Checklist:\n\n<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->\n<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->\n\n- [ ] I have signed the [Adobe Open Source CLA](https://opensource.adobe.com/cla.html).\n- [ ] My code follows the code style of this project.\n- [ ] My change requires a change to the documentation.\n- [ ] I have updated the documentation accordingly.\n- [ ] I have read the **CONTRIBUTING** document.\n- [ ] I have added tests to cover my changes.\n- [ ] All new and existing tests passed.\n"
  },
  {
    "path": ".github/workflows/ci.yaml",
    "content": "name: CI\n\non:\n  push:\n    branches:\n      - main\n      - feature/*\n  pull_request:\n    branches: [main]\n\njobs:\n  build:\n    runs-on: ubuntu-24.04\n    strategy:\n      matrix:\n        bazel-version:\n          - 7.6.0\n          - 8.5.0\n          - 9.0.0\n    env:\n      USE_BAZEL_VERSION: ${{ matrix.bazel-version }}\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v3\n      - name: Mount Bazel cache\n        uses: actions/cache@v4\n        with:\n          path: ~/.cache/bazel\n          key: bazel-${{ matrix.bazel-version }}-${{ github.sha }}\n          restore-keys: |\n            bazel-${{ matrix.bazel-version }}-\n            bazel-\n      - name: Setup build tools\n        run: |\n          TOOLS_DIR=\"${HOME}/.cache/tools\"\n          mkdir -p \"${TOOLS_DIR}/bin/\"\n          curl -Ls -o \"${TOOLS_DIR}/bin/bazel\" \"https://github.com/bazelbuild/bazelisk/releases/download/v1.14.0/bazelisk-linux-amd64\"\n          chmod +x \"${TOOLS_DIR}/bin/bazel\"\n          echo \"${TOOLS_DIR}/bin\" >> $GITHUB_PATH\n      - name: Test\n        run: |\n          bazel --output_base=~/.bazel/ test //... --config=ci\n      - name: Build helloworld\n        run: |\n          cd examples/helloworld\n          bazel --output_base=~/.bazel/ build //...\n      - name: Build legacy docker\n        if: matrix.bazel-version == '7.6.0'\n        run: |\n          cd examples/legacy_docker\n          bazel --output_base=~/.bazel/ build //...\n"
  },
  {
    "path": ".github/workflows/conventional-commits.yaml",
    "content": "# This helps the tag.yaml action to automatically create new releases\n#\n# tag.yaml requires all commits to follow the conventional commit pattern so that it can\n# automatically derive the next release version based on the commit history\n\nname: Verify PR title/description\non:\n  pull_request_target:\n    types:\n      - opened\n      - edited\n      - synchronize\npermissions:\n  pull-requests: read\njobs:\n  check:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: amannn/action-semantic-pull-request@v5\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}"
  },
  {
    "path": ".github/workflows/publish.yaml",
    "content": "# Publish new releases to Bazel Central Registry.\nname: Publish to BCR\non:\n  # Run the publish workflow after a successful release\n  # Will be triggered from the release.yaml workflow\n  workflow_call:\n    inputs:\n      tag_name:\n        required: true\n        type: string\n    secrets:\n      publish_token:\n        required: true\n  # In case of problems, let release engineers retry by manually dispatching\n  # the workflow from the GitHub UI\n  workflow_dispatch:\n    inputs:\n      tag_name:\n        description: git tag being released\n        required: true\n        type: string\njobs:\n  publish:\n    uses: bazel-contrib/publish-to-bcr/.github/workflows/publish.yaml@v1.0.0\n    with:\n      tag_name: ${{ inputs.tag_name }}\n      # GitHub repository which is a fork of the upstream where the Pull Request will be opened.\n      registry_fork: adobe/bazel-central-registry\n    permissions:\n      attestations: write\n      contents: write\n      id-token: write\n    secrets:\n      # Necessary to push to the BCR fork, and to open a pull request against a registry\n      publish_token: ${{ secrets.publish_token || secrets.BCR_PUBLISH_TOKEN }}"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "# Cut a release whenever a new tag is pushed to the repo.\nname: Release\non:\n  # Can be triggered from the tag.yaml workflow\n  workflow_call:\n    inputs:\n      tag_name:\n        required: true\n        type: string\n    secrets:\n      publish_token:\n        required: true\n  # Or, developers can manually push a tag from their clone\n  push:\n    tags:\n      - \"v*.*.*\"\npermissions:\n  id-token: write\n  attestations: write\n  contents: write\njobs:\n  release:\n    uses: bazel-contrib/.github/.github/workflows/release_ruleset.yaml@v7.2.2\n    with:\n      release_files: rules_gitops-*.tar.gz\n      prerelease: false\n      tag_name: ${{ inputs.tag_name || github.ref_name }}\n  publish:\n    needs: release\n    uses: ./.github/workflows/publish.yaml\n    with:\n      tag_name: ${{ inputs.tag_name || github.ref_name }}\n    secrets:\n      publish_token: ${{ secrets.publish_token || secrets.BCR_PUBLISH_TOKEN }}"
  },
  {
    "path": ".github/workflows/release_prep.sh",
    "content": "#!/usr/bin/env bash\n\nset -o errexit -o nounset -o pipefail\n\n# Argument provided by reusable workflow caller, see\n# https://github.com/bazel-contrib/.github/blob/d197a6427c5435ac22e56e33340dff912bc9334e/.github/workflows/release_ruleset.yaml#L72\nTAG=$1\n# The prefix is chosen to match what GitHub generates for source archives\n# This guarantees that users can easily switch from a released artifact to a source archive\n# with minimal differences in their code (e.g. strip_prefix remains the same)\nPREFIX=\"rules_gitops-${TAG:1}\"\nARCHIVE=\"rules_gitops-$TAG.tar.gz\"\n\n# NB: configuration for 'git archive' is in /.gitattributes\ngit archive --format=tar --prefix=${PREFIX}/ ${TAG} | gzip > $ARCHIVE\nSHA=$(shasum -a 256 $ARCHIVE | awk '{print $1}')\n\n# Add generated API docs to the release, see https://github.com/bazelbuild/bazel-central-registry/issues/5593\ndocs=\"$(mktemp -d)\"; targets=\"$(mktemp)\"\nbazel --output_base=\"$docs\" query --output=label --output_file=\"$targets\" 'kind(\"starlark_doc_extract rule\", //...)'\nbazel --output_base=\"$docs\" build --target_pattern_file=\"$targets\"\ntar --create --auto-compress \\\n    --directory \"$(bazel --output_base=\"$docs\" info bazel-bin)\" \\\n    --file \"$GITHUB_WORKSPACE/${ARCHIVE%.tar.gz}.docs.tar.gz\" .\n\ncat << EOF\n## Using Bzlmod with Bazel 7 or greater\nAdd to your \\`MODULE.bazel\\` file:\n\n\\`\\`\\`starlark\nbazel_dep(name = \"rules_gitops\", version = \"${TAG:1}\")\n\\`\\`\\`\n\nEOF\n"
  },
  {
    "path": ".gitignore",
    "content": ".vscode/\n.idea/\n.ijwb/\n.DS_Store\nThumbs.db\nbazel-*\n.bazelrc.user\n"
  },
  {
    "path": "BUILD.bazel",
    "content": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License. You may obtain a copy\n# of the License at http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software distributed under\n# the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n# OF ANY KIND, either express or implied. See the License for the specific language\n# governing permissions and limitations under the License.\n\n# gazelle:exclude examples\n# gazelle:map_kind bzl_library bzl_library @bazel_lib//:bzl_library.bzl\n\nload(\"@buildifier_prebuilt//:rules.bzl\", \"buildifier\", \"buildifier_test\")\nload(\"@gazelle//:def.bzl\", \"DEFAULT_LANGUAGES\", \"gazelle\", \"gazelle_binary\")\nload(\"@package_metadata//licenses/rules:license.bzl\", \"license\")\nload(\"@package_metadata//purl:purl.bzl\", \"purl\")\nload(\"@package_metadata//rules:package_metadata.bzl\", \"package_metadata\")\nload(\"//kustomize:defs.bzl\", \"kustomize_binary\")\n\nbuildifier(\n    name = \"buildifier.fix\",\n    exclude_patterns = [\"./.git/*\"],\n    lint_mode = \"fix\",\n    mode = \"fix\",\n)\n\nbuildifier_test(\n    name = \"buildifier_test\",\n    exclude_patterns = [\"./.git/*\"],\n    lint_mode = \"warn\",\n    mode = \"check\",\n    no_sandbox = True,\n    workspace = \":MODULE.bazel\",\n)\n\npackage_metadata(\n    name = \"package_metadata\",\n    purl = purl.bazel(\n        module_name(),\n        module_version(),\n    ),\n    visibility = [\"//visibility:public\"],\n)\n\nlicense(\n    name = \"license\",\n    kind = \"@package_metadata//licenses/spdx:Apache-2.0\",\n    text = \"LICENSE\",\n)\n\ngazelle_binary(\n    name = \"gazelle_bin\",\n    languages = DEFAULT_LANGUAGES + [\n        \"@bazel_skylib_gazelle_plugin//bzl\",\n    ],\n)\n\ngazelle(\n    name = \"gazelle\",\n    gazelle = \"gazelle_bin\",\n)\n\nkustomize_binary(\n    name = \"kustomize_bin\",\n)\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Adobe Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to making participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, gender identity and expression, level of experience,\nnationality, personal appearance, race, religion, or sexual identity and\norientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language.\n* Being respectful of differing viewpoints and experiences.\n* Gracefully accepting constructive criticism.\n* Focusing on what is best for the community.\n* Showing empathy towards other community members.\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\nadvances.\n* Trolling, insulting/derogatory comments, and personal or political attacks.\n* Public or private harassment.\n* Publishing others' private information, such as a physical or electronic\n  address, without explicit permission.\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting.\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces\nwhen an individual is representing the project or its community. Examples of\nrepresenting a project or community include using an official project e-mail\naddress, posting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event. Representation of a project may be\nfurther defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at Grp-opensourceoffice@adobe.com. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at [https://contributor-covenant.org/version/1/4][version].\n\n[homepage]: https://contributor-covenant.org\n[version]: https://contributor-covenant.org/version/1/4/\n"
  },
  {
    "path": "COPYRIGHT",
    "content": "© Copyright 2015-2026 Adobe. All rights reserved.\n\nAdobe holds the copyright for all the files found in this repository.\n\nSee the LICENSE file for licensing information.\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2020 Adobe\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n\n   -----------------------------------------------------------------------\n\n   The contents of third party dependencies in /vendor folder are covered\n   by their repositories' respective licenses.\n\n   The contents of /templating/fasttemplate folder are licensed under\n   MIT License.\n"
  },
  {
    "path": "MODULE.bazel",
    "content": "\"adobe/rules_gitops\"\n\nmodule(\n    name = \"rules_gitops\",\n    version = \"\",\n    compatibility_level = 0,\n)\n\nbazel_dep(name = \"platforms\", version = \"1.0.0\")\nbazel_dep(name = \"package_metadata\", version = \"0.0.6\")\nbazel_dep(name = \"protobuf\", version = \"33.4\")\nbazel_dep(name = \"rules_go\", version = \"0.59.0\")\nbazel_dep(name = \"rules_shell\", version = \"0.6.1\")\n\ngo_sdk = use_extension(\"@rules_go//go:extensions.bzl\", \"go_sdk\")\ngo_sdk.download(version = \"1.24.12\")\n\nbazel_dep(name = \"gazelle\", version = \"0.47.0\")\n\ngo_deps = use_extension(\"@gazelle//:extensions.bzl\", \"go_deps\")\ngo_deps.from_file(go_mod = \"//:go.mod\")\nuse_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\")\n\nbazel_dep(name = \"bazel_skylib\", version = \"1.8.2\")\nbazel_dep(name = \"bazel_lib\", version = \"3.0.0\")\nbazel_dep(name = \"bazelrc-preset.bzl\", version = \"1.6.0\")\n\nkustomize = use_extension(\"//kustomize:defs.bzl\", \"kustomize\")\nuse_repo(kustomize, \"kustomize\")\n\nregister_toolchains(\"@kustomize//toolchains:all\")\n\nkubectl = use_extension(\"//kubectl:defs.bzl\", \"kubectl\")\nuse_repo(kubectl, \"kubectl\")\n\nregister_toolchains(\"@kubectl//toolchains:all\")\n\nbazel_dep(name = \"rules_img\", version = \"0.3.3\")\n\n# Dev Dependencies\nbazel_dep(name = \"bazel_skylib_gazelle_plugin\", version = \"1.8.2\", dev_dependency = True)\nbazel_dep(name = \"buildifier_prebuilt\", version = \"8.2.1\", dev_dependency = True)\n\npull = use_repo_rule(\"@rules_img//img:pull.bzl\", \"pull\")\n\npull(\n    name = \"alpine\",\n    dev_dependency = True,\n    digest = \"sha256:51183f2cfa6320055da30872f211093f9ff1d3cf06f39a0bdb212314c5dc7375\",\n    layer_handling = \"lazy\",\n    registries = [\n        \"mirror.gcr.io\",\n        \"index.docker.io\",\n    ],\n    repository = \"library/alpine\",\n    tag = \"3.23\",\n)\n\npreset = use_repo_rule(\"//tools:preset.bzl\", \"preset\")\n\npreset(\n    name = \"preset.bzl\",\n)\n"
  },
  {
    "path": "README.md",
    "content": "# Bazel GitOps Rules\n\n![CI](https://github.com/adobe/rules_gitops/workflows/CI/badge.svg?branch=main&event=push)\n\nBazel 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.\n\n## Features\n\n- **Kustomize Integration**: Full [Kustomize](https://kustomize.io/) capabilities for generating and transforming manifests\n- **GitOps Workflow**: Native support for Git-based deployment workflows with automatic PR creation\n- **Container Image Management**: Seamless integration with [rules_img](https://github.com/bazel-contrib/rules_img) for building and pushing container images\n- **Namespace Deployments**: Support for personal and team namespace deployments\n- **Integration Testing**: Built-in utilities for Kubernetes integration test setup\n- **Toolchain Support**: Managed kustomize and kubectl toolchains via Bzlmod\n\n## Ruleset Overview\n\nThe ruleset is organized into the following modules:\n\n| Module | Description |\n|--------|-------------|\n| `@rules_gitops//gitops:defs.bzl` | Core deployment rules (`k8s_deploy`, `gitops`) for rendering manifests and managing deployments |\n| `@rules_gitops//kustomize:defs.bzl` | Kustomize rules and toolchain for manifest transformation |\n| `@rules_gitops//kubectl:defs.bzl` | Kubectl toolchain for cluster interactions |\n| `@rules_gitops//testing:defs.bzl` | Integration testing utilities (`k8s_test_setup`, `k8s_test_namespace`) |\n| `@rules_gitops//adapters:providers.bzl` | `K8sPushInfo` provider for container image integration |\n| `@rules_gitops//adapters:rules_img.bzl` | Adapter for [rules_img](https://github.com/bazel-contrib/rules_img) container images |\n\n## Getting Started\n\n### Installation\n\nAdd `rules_gitops` to your `MODULE.bazel`:\n\n```starlark\nbazel_dep(name = \"rules_gitops\", version = \"1.0.0\")\n```\n\n### Toolchain Setup\n\nThe ruleset provides managed toolchains for kustomize and kubectl. To use custom versions, configure the extensions in your `MODULE.bazel`:\n\n```starlark\nkustomize = use_extension(\"@rules_gitops//kustomize:defs.bzl\", \"kustomize\")\nkustomize.toolchain(\n    version = \"5.7.1\",  # specify your desired version\n)\n\nkubectl = use_extension(\"@rules_gitops//kubectl:defs.bzl\", \"kubectl\")\nkubectl.toolchain(\n    version = \"1.32.2\",  # specify your desired version\n)\n```\n\n<a name=\"k8s_deploy\"></a>\n## k8s_deploy\n\nThe `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.\n\nFor 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:\n```bash\ncd examples\nbazel run //:mynamespace.show\n```\n\n### Container Image Integration\n\n`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:\n\n```starlark\nload(\"@rules_gitops//adapters:rules_img.bzl\", \"k8s_push_info\")\nload(\"@rules_gitops//gitops:defs.bzl\", \"k8s_deploy\")\nload(\"@rules_img//img:image.bzl\", \"image_manifest\")\nload(\"@rules_img//img:push.bzl\", \"image_push\")\n\nimage_manifest(\n    name = \"my_image\",\n    # ... image configuration\n)\n\nimage_push(\n    name = \"push\",\n    image = \":my_image\",\n    registry = \"my-registry.com\",\n    repository = \"my-org/my-image\",\n)\n\nk8s_push_info(\n    name = \"k8s_image\",\n    image = \":my_image\",\n    push = \":push\",\n    registry = \"my-registry.com\",\n    repository = \"my-org/my-image\",\n)\n\nk8s_deploy(\n    name = \"my_deployment\",\n    cluster = \"my-cluster\",\n    images = [\":k8s_image\"],\n    manifests = [\"deployment.yaml\"],\n    namespace = \"{BUILD_USER}\",\n)\n```\n\n### Basic Usage\n\nOnce configured, you can use the generated targets:\n\n```bash\n# Show rendered manifests (useful for debugging)\nbazel run //path/to:my_deployment.show\n\n# Apply manifests to cluster using your kubectl toolchain\nbazel run //path/to:my_deployment.apply\n\n# Generate GitOps output\nbazel run //path/to:my_deployment.gitops\n```\n\nSee the [examples/helloworld](./examples/helloworld) directory for a complete working example.\n\n<a name=\"gitops-workflow\"></a>\n## GitOps Workflow\n\nThe `create_gitops_prs` tool automates the creation of GitOps pull requests as part of your CI pipeline:\n\nThe simplified CI pipeline that incorporates GitOps will look like this:\n```\n[Checkout Code] -> [Bazel Build & Test] -> (if GitOps source branch) -> [Create GitOps PRs]\n```\n\nThe *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.\n\nFor the full list of `create_gitops_prs` command line options, run:\n```bash\nbazel run @rules_gitops//gitops/prer:create_gitops_prs\n```\n\n<a name=\"gitops-and-deployment-supported-git-servers\"></a>\n### Supported Git Servers\n\nThe `--git_server` parameter defines the type of a Git server API to use. The supported Git server types are `github`, `gitlab`, and `bitbucket`.\n\nDepending on the Git server type the `create_gitops_prs` tool will use following command line parameters:\n\n--git_server | Parameter                            | Default\n------------ | ------------------------------------ | --------------\n| `github`\n|            | ***--github_repo_owner***            | ``\n|            | ***--github_repo***                  | ``\n|            | ***--github_access_token***          | `$GITHUB_TOKEN`\n|            | ***--github_enterprise_host***       | ``\n| `gitlab`   |\n|            | ***--gitlab_host***                  | `https://gitlab.com`\n|            | ***--gitlab_repo***                  | ``\n|            | ***--gitlab_access_token***          | `$GITLAB_TOKEN`\n| `bitbucket`\n|            | ***--bitbucket_api_pr_endpoint***    | ``\n|            | ***--bitbucket_user***               | `$BITBUCKET_USER`\n|            | ***--bitbucket_password***           | `$BITBUCKET_PASSWORD`\n\n<a name=\"trunk-based-gitops-workflow\"></a>\n## Trunk Based GitOps Workflow\n\nLet'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.\n\nThe *Create GitOps PRs* pipeline step shell command will look like following:\n```bash\nGIT_ROOT_DIR=$(git rev-parse --show-toplevel)\nGIT_COMMIT_ID=$(git rev-parse HEAD)\nGIT_BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)\nif [ \"${GIT_BRANCH_NAME}\" == \"master\"]; then\n    bazel run //gitops/prer:create_gitops_prs -- \\\n        --workspace $GIT_ROOT_DIR \\\n        --git_repo https://github.com/example/repo.git \\\n        --git_mirror $GIT_ROOT_DIR/.git \\\n        --git_server github \\\n        --release_branch master \\\n        --gitops_pr_into master \\\n        --gitops_pr_title \"This is my pull request title\" \\\n        --gitops_pr_body \"This is my pull request body message\" \\\n        --branch_name ${GIT_BRANCH_NAME} \\\n        --git_commit ${GIT_COMMIT_ID} \\\nfi\n```\n\nThe `GIT_*` variables describe the current state of the Git repository.\n\nThe `--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.\n\nThe `--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.\n\nThe `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.\n\nThe 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.\n\nFor example, we define two deployments: grafana and prometheus. Both deployments share the same namespace. The deployments are grouped by namespace.\n```starlark\n[\n    k8s_deploy(\n        name = NAME,\n        deploy_branch = NAMESPACE,\n        ...\n    )\n    for NAME, CLUSTER, NAMESPACE in [\n        ...\n        (\"stage-grafana\", \"stage\", \"monitoring-stage\"),\n        (\"prod-grafana\", \"prod\", \"monitoring-prod\"),\n    ]\n]\n[\n    k8s_deploy(\n        name = NAME,\n        deploy_branch = NAMESPACE,\n        ...\n    )\n    for NAME, CLUSTER, NAMESPACE in [\n        ...\n        (\"stage-prometheus\", \"stage\", \"monitoring-stage\"),\n        (\"prod-prometheus\", \"prod\", \"monitoring-prod\"),\n    ]\n]\n```\n\nAs a result of the setup above the `create_gitops_prs` tool will open up to 2 potential deployment pull requests:\n* from `deploy/monitoring-stage` to `main` including manifests for `stage-grafana` and `stage-prometheus`\n* from `deploy/monitoring-prod` to `main` including manifests for `prod-grafana` and `prod-prometheus`\n\nThe 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.\n\nThe `--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.\n\nHere are the placeholders that can be replaced:\n\n| Placeholder      | Replacement                                    |\n|------------------|-------------------------------------------------|\n| `{{GIT_REVISION}}` | Result of `git rev-parse HEAD`                  |\n| `{{UTC_DATE}}`     | Result of `date -u`                             |\n| `{{GIT_BRANCH}}`   | The `branch_name` argument given to `create_gitops_prs` |\n\n`--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.\n\n<a name=\"multiple-release-branches-gitops-workflow\"></a>\n## Multiple Release Branches GitOps Workflow\n\nIn 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.\n\nBoth trunk and release branch workflows can coexist in the same repository.\n\nFor 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.\n\nThe *Create GitOps PRs* pipeline step shell command will look like following:\n```bash\nGIT_ROOT_DIR=$(git rev-parse --show-toplevel)\nGIT_COMMIT_ID=$(git rev-parse HEAD)\nGIT_BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)          # => release/team-20200101\nRELEASE_BRANCH_SUFFIX=${GIT_BRANCH_NAME#\"release/team\"}     # => -20200101\nRELEASE_BRANCH=${GIT_BRANCH_NAME%${RELEASE_BRANCH_SUFFIX}}  # => release/team\nif [ \"${RELEASE_BRANCH}\" == \"release/team\"]; then\n    bazel run //gitops/prer:create_gitops_prs -- \\\n        --workspace $GIT_ROOT_DIR \\\n        --git_repo https://github.com/example/repo.git \\\n        --git_mirror $GIT_ROOT_DIR/.git \\\n        --git_server github \\\n        --release_branch ${RELEASE_BRANCH} \\\n        --deployment_branch_suffix=${RELEASE_BRANCH_SUFFIX} \\\n        --gitops_pr_into master \\\n        --gitops_pr_title \"This is my pull request title\" \\\n        --gitops_pr_body \"This is my pull request body message\" \\\n        --branch_name ${GIT_BRANCH_NAME} \\\n        --git_commit ${GIT_COMMIT_ID} \\\nfi\n```\n\nThe meaning of the parameters is the same as with [trunk based workflow](#trunk_based_gitops_workflow).\nThe `--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.\n\nIf we modify the previous example:\n```starlark\n[\n    k8s_deploy(\n        name = NAME,\n        deploy_branch = NAMESPACE,\n        release_branch_prefix = \"release/team\",  # will only be selected when --release_branch=release/team\n        ...\n    )\n    for NAME, CLUSTER, NAMESPACE in [\n        ...\n        (\"stage-grafana\", \"stage\", \"monitoring-stage\"),\n        (\"prod-grafana\", \"prod\", \"monitoring-prod\"),\n    ]\n]\n[\n    k8s_deploy(\n        name = NAME,\n        deploy_branch = NAMESPACE,\n        release_branch_prefix = \"release/team\",  # will only be selected when --release_branch=release/team\n        ...\n    )\n    for NAME, CLUSTER, NAMESPACE in [\n        ...\n        (\"stage-prometheus\", \"stage\", \"monitoring-stage\"),\n        (\"prod-prometheus\", \"prod\", \"monitoring-prod\"),\n    ]\n]\n```\n\nThe 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`:\n* from `deploy/monitoring-stage-20200101` to `master` including manifests for `stage-grafana` and `stage-prometheus`\n* from `deploy/monitoring-prod-20200101` to `master` including manifests for `prod-grafana` and `prod-prometheus`\n\n\n<a name=\"integration-testing-support\"></a>\n## Integration Testing Support\n\n**Note:** the Integration testing support has known limitations and should be considered **experimental**. The public API will not abide by semver.\n\nIntegration tests are defined in `BUILD.bazel` files like this:\n```starlark\nk8s_test_setup(\n    name = \"service_it.setup\",\n    kubeconfig = \"@k8s_test//:kubeconfig\",\n    objects = [\n        \"//service:mynamespace\",\n    ],\n)\n\njava_test(\n    name = \"service_it\",\n    srcs = [\n        \"ServiceIT.java\",\n    ],\n    data = [\n        \":service_it.setup\",\n    ],\n    jvm_flags = [\n        \"-Dk8s.setup=$(location :service_it.setup)\",\n    ],\n    # other attributes omitted for brevity\n)\n```\nThe 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.\n\nThe `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.\n\nThe 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.\n\nThe 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.\n\nThe `@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.\n\n```starlark\nload(\"//gitops:defs.bzl\", \"kubeconfig\")\n\nkubeconfig(\n    name = \"k8s_test\",\n    cluster = \"dev\",\n)\n```\n\n## Building & Testing\n\n### Building & Testing GitOps Rules\n\n```bash\nbazel test //...\n```\n\n### Building & Testing Examples Project\n\n```bash\ncd examples/helloworld\nbazel test //...\n```\n\n## Have a Question\n\nFind the `rules_gitops` contributors in the [#gitops](https://bazelbuild.slack.com/archives/C01SF68MTFS) channel on the [Bazel Slack](https://slack.bazel.build/).\n\n## Contributing\n\nContributions are welcomed! Read the [Contributing Guide](./.github/CONTRIBUTING.md) for more information.\n\n\n## Licensing\n\nThe contents of [/templating/fasttemplate](./templating/fasttemplate) are licensed under MIT License. See [LICENSE](./templating/fasttemplate/LICENSE) for more information.\n\nAll other files are licensed under the Apache V2 License. See [LICENSE](LICENSE) for more information.\n"
  },
  {
    "path": "WORKSPACE.bazel",
    "content": "# Marker that this is the root of a Bazel workspace.\n"
  },
  {
    "path": "adapters/BUILD.bazel",
    "content": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\n\nbzl_library(\n    name = \"providers\",\n    srcs = [\"providers.bzl\"],\n    visibility = [\"//visibility:public\"],\n)\n\nbzl_library(\n    name = \"rules_img\",\n    srcs = [\"rules_img.bzl\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \":providers\",\n        \"@rules_img//img:providers\",\n    ],\n)\n\nbzl_library(\n    name = \"external_image\",\n    srcs = [\"external_image.bzl\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\":providers\"],\n)\n"
  },
  {
    "path": "adapters/external_image.bzl",
    "content": "\"\"\"\nImplementation of external image information provider suitable for injection into manifests\n\"\"\"\n\nload(\":providers.bzl\", \"K8sPushInfo\")\n\ndef _external_image_impl(ctx):\n    sv = ctx.attr.image.split(\"@\", 1)\n    if (len(sv) == 1) and (not ctx.attr.digest):\n        fail(\"digest must be specified either in image or as a separate attribute\")\n    s = sv[0].split(\":\", 1)[0]  #drop tag\n    registry, repository = s.split(\"/\", 1)\n\n    #write digest to a file\n    digest_file = ctx.actions.declare_file(ctx.label.name + \".digest\")\n    ctx.actions.write(\n        output = digest_file,\n        content = ctx.attr.digest,\n    )\n    return [\n        DefaultInfo(\n            files = depset([digest_file]),\n        ),\n        K8sPushInfo(\n            image_label = ctx.label,\n            legacy_image_name = ctx.attr.image_name,\n            registry = registry,\n            repository = repository,\n            digestfile = digest_file,\n        ),\n    ]\n\nexternal_image = rule(\n    implementation = _external_image_impl,\n    attrs = {\n        \"image\": attr.string(mandatory = True, doc = \"The image location, e.g. gcr.io/foo/bar:baz\"),\n        \"image_name\": attr.string(doc = \"Image name, e.g. exernalserver. DEPRECATED: Use full target label instead, e.g. //images:externalserver\"),\n        \"digest\": attr.string(mandatory = True, doc = \"The image digest, e.g. sha256:deadbeef\"),\n    },\n)\n"
  },
  {
    "path": "adapters/providers.bzl",
    "content": "\"\"\"Provider definitions for container image adapters.\"\"\"\n\nK8sPushInfo = provider(\n    doc = \"Information required to inject image into a manifest and optionally push it\",\n    fields = {\n        \"image_label\": \"bazel target label of the image\",\n        \"legacy_image_name\": \"Optional: short name\",\n        \"registry\": \"registry where the image resides\",\n        \"repository\": \"repository where the image resides\",\n        \"digestfile\": \"a file containing the digest of the image\",\n        \"pusher\": \"Optional: an executable target used to push the image to a remote registry\",\n        \"run_environment\": \"Optional: a run environment info provider used for pushing the image\",\n    },\n)\n"
  },
  {
    "path": "adapters/rules_img.bzl",
    "content": "\"\"\"Adapter for rules_img container images.\"\"\"\n\nload(\"@rules_img//img:providers.bzl\", \"DeployInfo\", \"ImageIndexInfo\", \"ImageManifestInfo\")\nload(\"//adapters:providers.bzl\", \"K8sPushInfo\")\n\ndef _k8s_push_info_impl(ctx):\n    digestfile = ctx.attr.image[OutputGroupInfo].digest.to_list()[0]\n\n    return [\n        DefaultInfo(),\n        K8sPushInfo(\n            image_label = ctx.attr.image.label,\n            registry = ctx.attr.registry,\n            repository = ctx.attr.repository,\n            digestfile = digestfile,\n            pusher = ctx.attr.push[DefaultInfo],\n            run_environment = ctx.attr.push[RunEnvironmentInfo],\n        ),\n    ]\n\nk8s_push_info = rule(\n    implementation = _k8s_push_info_impl,\n    attrs = {\n        \"image\": attr.label(mandatory = True, providers = [[ImageManifestInfo], [ImageIndexInfo]]),\n        \"push\": attr.label(mandatory = True, cfg = \"target\", providers = [DeployInfo]),\n        \"registry\": attr.string(mandatory = True),\n        \"repository\": attr.string(mandatory = True),\n    },\n)\n"
  },
  {
    "path": "e2e/BUILD.bazel",
    "content": "load(\"//e2e/util:util.bzl\", \"e2e_test\", \"kubectl_cmd\")\n\nkubectl_cmd(\n    name = \"verify_application\",\n    args = [\"-n \\\"$${USER}\\\" wait --timeout=180s --for=condition=Available deployment.apps/helloworld\"],\n)\n\ne2e_test(\n    name = \"manual_k8s\",\n    steps = [\n        \"//e2e/deployment:mynamespace.apply\",\n        \"//e2e:verify_application\",\n        \"//e2e/deployment:mynamespace.delete\",\n    ],\n    tags = [\n        \"skip-bazel-ci\",\n    ],\n)\n"
  },
  {
    "path": "e2e/deployment/BUILD.bazel",
    "content": "load(\"@rules_gitops//adapters:rules_img.bzl\", \"k8s_push_info\")\nload(\"@rules_gitops//gitops:defs.bzl\", \"k8s_deploy\")\nload(\"@rules_go//go:def.bzl\", \"go_binary\", \"go_library\")\nload(\"@rules_img//img:image.bzl\", \"image_manifest\")\nload(\"@rules_img//img:layer.bzl\", \"image_layer\")\nload(\"@rules_img//img:push.bzl\", \"image_push\")\n\nCLUSTER = \"kind-kind\"\n\nUSER = \"kind-kind\"\n\nREGISTRY = \"localhost:15000\"\n\npackage(\n    default_visibility = [\"//e2e:__subpackages__\"],\n)\n\nplatform(\n    name = \"linux_amd64\",\n    constraint_values = [\n        \"@platforms//os:linux\",\n        \"@platforms//cpu:x86_64\",\n    ],\n)\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"helloworld.go\"],\n    importpath = \"github.com/adobe/rules_gitops/e2e/deployment\",\n)\n\ngo_binary(\n    name = \"helloworld\",\n    embed = [\":go_default_library\"],\n    # Use pure Go (no CGO) to create a static binary compatible with Alpine (musl libc)\n    pure = \"on\",\n)\n\nimage_layer(\n    name = \"go_layer\",\n    srcs = {\n        \"/bin/app\": \":helloworld\",\n    },\n)\n\nimage_manifest(\n    name = \"go_image\",\n    base = \"@alpine\",\n    entrypoint = [\"/bin/app\"],\n    layers = [\n        \":go_layer\",\n    ],\n    platform = \":linux_amd64\",\n)\n\nimage_push(\n    name = \"push\",\n    image = \":go_image\",\n    registry = REGISTRY,\n    repository = \"rules_gitops/e2e/deployment/helloworld\",\n)\n\nk8s_push_info(\n    name = \"k8s_image\",\n    image = \":go_image\",\n    push = \":push\",\n    registry = REGISTRY,\n    repository = \"rules_gitops/e2e/deployment/helloworld\",\n)\n\nk8s_deploy(\n    name = \"mynamespace\",\n    cluster = CLUSTER,\n    images = [\":k8s_image\"],\n    manifests = [\n        \"manifests/deployment.yaml\",\n        \"manifests/service.yaml\",\n    ],\n    namespace = \"{BUILD_USER}\",\n    user = USER,\n)\n\nk8s_deploy(\n    name = \"gitops\",\n    cluster = CLUSTER,\n    deployment_branch = \"e2e-deployment\",\n    gitops = True,\n    images = [\":k8s_image\"],\n    manifests = [\n        \"manifests/deployment.yaml\",\n        \"manifests/service.yaml\",\n    ],\n    namespace = \"hwteam\",\n    release_branch_prefix = \"main\",\n    user = USER,\n)\n"
  },
  {
    "path": "e2e/deployment/helloworld.go",
    "content": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\npackage main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n)\n\nvar (\n\tport = flag.Int(\"port\", 8080, \"IP port\")\n)\n\nfunc printenv(w http.ResponseWriter, r *http.Request) {\n\tfor _, e := range os.Environ() {\n\t\tfmt.Fprintf(w, \"%s\\n\", e)\n\t}\n}\n\nfunc home(w http.ResponseWriter, r *http.Request) {\n\tio.WriteString(w, \"<html><body>Hello World!</body></html>\")\n}\n\nfunc main() {\n\tflag.Parse()\n\thttp.HandleFunc(\"/\", home)\n\thttp.HandleFunc(\"/healthz\", func(w http.ResponseWriter, r *http.Request) {\n\t\tfmt.Fprintf(w, \"ok\\n\")\n\t})\n\thttp.HandleFunc(\"/env\", printenv)\n\tfmt.Printf(\"Serving on port %d\\n\", *port)\n\tlog.Fatal(http.ListenAndServe(fmt.Sprintf(\":%d\", *port), nil))\n}\n"
  },
  {
    "path": "e2e/deployment/manifests/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: helloworld\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: helloworld\n  template:\n    metadata:\n      labels:\n        app: helloworld\n    spec:\n      containers:\n        - name: helloworld\n          image: //e2e/deployment:go_image\n          args:\n            - --port=8080\n          ports:\n            - containerPort: 8080\n              name: web\n          resources:\n            requests:\n              memory: 2Mi\n          readinessProbe:\n            httpGet:\n              path: /healthz\n              port: 8080\n          env:\n            - name: NODE_NAME\n              valueFrom:\n                fieldRef:\n                  fieldPath: spec.nodeName\n            - name: POD_NAME\n              valueFrom:\n                fieldRef:\n                  fieldPath: metadata.name\n            - name: POD_NAMESPACE\n              valueFrom:\n                fieldRef:\n                  fieldPath: metadata.namespace\n            - name: POD_IP\n              valueFrom:\n                fieldRef:\n                  fieldPath: status.podIP\n"
  },
  {
    "path": "e2e/deployment/manifests/service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: helloworld\n  labels:\n    app: helloworld\nspec:\n  ports:\n    - port: 80\n      name: web\n      targetPort: 8080\n  selector:\n    app: helloworld\n"
  },
  {
    "path": "e2e/util/BUILD.bazel",
    "content": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\nload(\"@rules_shell//shell:sh_binary.bzl\", \"sh_binary\")\n\npackage(\n    default_visibility = [\"//e2e:__subpackages__\"],\n)\n\nexports_files(\n    [\n        \"test.sh\",\n        \"kubectl_cmd.sh.tpl\",\n    ],\n)\n\nsh_binary(\n    name = \"setup\",\n    srcs = [\"setup.sh\"],\n    data = [\n        \"//kubectl:resolved_toolchain\",\n        \"@io_k8s_sigs_kind//:kind\",\n    ],\n    env = {\n        \"KIND_BIN_PATH\": \"$(rlocationpath @io_k8s_sigs_kind//:kind)\",\n        \"KUBECTL_BIN_PATH\": \"$(rlocationpath //kubectl:resolved_toolchain)\",\n    },\n    toolchains = [\n        \"//kubectl:resolved_toolchain\",\n    ],\n    deps = [\n        \"@bazel_tools//tools/bash/runfiles\",\n    ],\n)\n\nsh_binary(\n    name = \"teardown\",\n    srcs = [\"teardown.sh\"],\n    data = [\n        \"//kubectl:resolved_toolchain\",\n        \"@io_k8s_sigs_kind//:kind\",\n    ],\n    env = {\n        \"KIND_BIN_PATH\": \"$(rlocationpath @io_k8s_sigs_kind//:kind)\",\n        \"KUBECTL_BIN_PATH\": \"$(rlocationpath //kubectl:resolved_toolchain)\",\n    },\n    deps = [\n        \"@bazel_tools//tools/bash/runfiles\",\n    ],\n)\n\nbzl_library(\n    name = \"util\",\n    srcs = [\"util.bzl\"],\n    deps = [\n        \"@bazel_lib//lib:expand_template\",\n        \"@rules_shell//shell:rules_bzl\",\n    ],\n)\n"
  },
  {
    "path": "e2e/util/kubectl_cmd.sh.tpl",
    "content": "#!/usr/bin/env bash\nset -o errexit\n\n# --- begin runfiles.bash initialization v3 ---\n# Copy-pasted from the Bazel Bash runfiles library v3.\nset -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash\n# shellcheck disable=SC1090\nsource \"${RUNFILES_DIR:-/dev/null}/$f\" 2>/dev/null || \\\nsource \"$(grep -sm1 \"^$f \" \"${RUNFILES_MANIFEST_FILE:-/dev/null}\" | cut -f2- -d' ')\" 2>/dev/null || \\\nsource \"$0.runfiles/$f\" 2>/dev/null || \\\nsource \"$(grep -sm1 \"^$f \" \"$0.runfiles_manifest\" | cut -f2- -d' ')\" 2>/dev/null || \\\nsource \"$(grep -sm1 \"^$f \" \"$0.exe.runfiles_manifest\" | cut -f2- -d' ')\" 2>/dev/null || \\\n{ echo>&2 \"ERROR: cannot find $f\"; exit 1; }; f=; set -e\n# --- end runfiles.bash initialization v3 ---\n\nKUBECTL_BIN=$(rlocation \"${KUBECTL_BIN_PATH}\")\nif [[ ! -f \"${KUBECTL_BIN}\" ]]; then\n  echo >&2 \"ERROR: could not find kubectl binary\"\n  exit 1\nfi\n\n\"${KUBECTL_BIN}\" $$KUBECTL_ARGS$$"
  },
  {
    "path": "e2e/util/setup.sh",
    "content": "#!/usr/bin/env bash\nset -o errexit\n\n# --- begin runfiles.bash initialization v3 ---\n# Copy-pasted from the Bazel Bash runfiles library v3.\nset -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash\n# shellcheck disable=SC1090\nsource \"${RUNFILES_DIR:-/dev/null}/$f\" 2>/dev/null || \\\nsource \"$(grep -sm1 \"^$f \" \"${RUNFILES_MANIFEST_FILE:-/dev/null}\" | cut -f2- -d' ')\" 2>/dev/null || \\\nsource \"$0.runfiles/$f\" 2>/dev/null || \\\nsource \"$(grep -sm1 \"^$f \" \"$0.runfiles_manifest\" | cut -f2- -d' ')\" 2>/dev/null || \\\nsource \"$(grep -sm1 \"^$f \" \"$0.exe.runfiles_manifest\" | cut -f2- -d' ')\" 2>/dev/null || \\\n{ echo>&2 \"ERROR: cannot find $f\"; exit 1; }; f=; set -e\n# --- end runfiles.bash initialization v3 ---\n\n# desired cluster name; default is \"kind\"\nKIND_CLUSTER_NAME=\"${KIND_CLUSTER_NAME:-kind}\"\n\nKIND_BIN=$(rlocation \"${KIND_BIN_PATH}\")\nif [[ ! -f \"${KIND_BIN}\" ]]; then\n  echo >&2 \"ERROR: could not find kind binary\"\n  exit 1\nfi\n\nKUBECTL_BIN=$(rlocation \"${KUBECTL_BIN_PATH}\")\nif [[ ! -f \"${KUBECTL_BIN}\" ]]; then\n  echo >&2 \"ERROR: could not find kubectl binary\"\n  exit 1\nfi\n\nDOCKER_BIN=$(which docker)\nif [[ ! -f \"${DOCKER_BIN}\" ]]; then\n  echo >&2 \"ERROR: could not find docker binary\"\n  exit 1\nfi\n\necho \"Using docker: ${DOCKER_BIN}\"\n\n# create registry container unless it already exists\nreg_name='kind-registry'\nreg_port='15000'\nrunning=\"$(docker inspect -f '{{.State.Running}}' \"${reg_name}\" 2>/dev/null || true)\"\nif [ \"${running}\" != 'true' ]; then\n  docker container rm \"${reg_name}\" 2>/dev/null || true\n  docker run \\\n    -d --restart=always -e \"REGISTRY_HTTP_ADDR=0.0.0.0:${reg_port}\" -p \"${reg_port}:${reg_port}\" --name \"${reg_name}\" \\\n    registry:3\nfi\n\n# create a cluster with the local registry enabled in containerd\ncat <<EOF | \"${KIND_BIN}\" create cluster --name \"${KIND_CLUSTER_NAME}\" --image \"kindest/node:v1.30.13\" --config=-\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\ncontainerdConfigPatches:\n- |-\n  [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"localhost:${reg_port}\"]\n    endpoint = [\"http://${reg_name}:${reg_port}\"]\nEOF\n\n# connect the registry to the cluster network\n# (the network may already be connected)\ndocker network connect \"${KIND_CLUSTER_NAME}\" \"${reg_name}\" || true\n\n\"${KUBECTL_BIN}\" config use-context kind-kind\n\n# Document the local registry\n# https://github.com/kubernetes/enhancements/tree/master/keps/sig-cluster-lifecycle/generic/1755-communicating-a-local-registry\ncat <<EOF | \"${KUBECTL_BIN}\" apply -f -\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: local-registry-hosting\n  namespace: kube-public\ndata:\n  localRegistryHosting.v1: |\n    host: \"localhost:${reg_port}\"\n    help: \"https://kind.sigs.k8s.io/docs/user/local-registry/\"\nEOF\n\n\n\nMYNAMESPACE=$USER\n\necho \"Creating namespaces ${MYNAMESPACE} and hwteam\"\n\n\"${KUBECTL_BIN}\" create namespace $MYNAMESPACE || true\n\"${KUBECTL_BIN}\" create namespace hwteam || true"
  },
  {
    "path": "e2e/util/teardown.sh",
    "content": "#!/usr/bin/env bash\nset -o errexit\n\n# --- begin runfiles.bash initialization v3 ---\n# Copy-pasted from the Bazel Bash runfiles library v3.\nset -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash\n# shellcheck disable=SC1090\nsource \"${RUNFILES_DIR:-/dev/null}/$f\" 2>/dev/null || \\\nsource \"$(grep -sm1 \"^$f \" \"${RUNFILES_MANIFEST_FILE:-/dev/null}\" | cut -f2- -d' ')\" 2>/dev/null || \\\nsource \"$0.runfiles/$f\" 2>/dev/null || \\\nsource \"$(grep -sm1 \"^$f \" \"$0.runfiles_manifest\" | cut -f2- -d' ')\" 2>/dev/null || \\\nsource \"$(grep -sm1 \"^$f \" \"$0.exe.runfiles_manifest\" | cut -f2- -d' ')\" 2>/dev/null || \\\n{ echo>&2 \"ERROR: cannot find $f\"; exit 1; }; f=; set -e\n# --- end runfiles.bash initialization v3 ---\n\nDOCKER_BIN=$(which docker)\nif [[ ! -f \"${DOCKER_BIN}\" ]]; then\n  echo >&2 \"ERROR: could not find docker binary\"\n  exit 1\nfi\n\n# desired cluster name; default is \"kind\"\nKIND_CLUSTER_NAME=\"${KIND_CLUSTER_NAME:-kind}\"\n\nKIND_BIN=$(rlocation \"${KIND_BIN_PATH}\")\nif [[ ! -f \"${KIND_BIN}\" ]]; then\n  echo >&2 \"ERROR: could not find kind binary\"\n  exit 1\nfi\n\nKUBECTL_BIN=$(rlocation \"${KUBECTL_BIN_PATH}\")\nif [[ ! -f \"${KUBECTL_BIN}\" ]]; then\n  echo >&2 \"ERROR: could not find kubectl binary\"\n  exit 1\nfi\n\n\"${KUBECTL_BIN}\" config use-context kind-kind\n\nMYNAMESPACE=\"${USER}\"\n\necho \"=== DEBUG: Cluster status ===\"\necho \"--- Nodes ---\"\n\"${KUBECTL_BIN}\" get nodes -o wide || true\n\necho \"--- All pods in namespace ${MYNAMESPACE} ---\"\n\"${KUBECTL_BIN}\" get pods -n \"${MYNAMESPACE}\" -o wide || true\n\necho \"--- Deployment status ---\"\n\"${KUBECTL_BIN}\" get deployments -n \"${MYNAMESPACE}\" -o wide || true\n\necho \"--- Describe deployment helloworld ---\"\n\"${KUBECTL_BIN}\" describe deployment helloworld -n \"${MYNAMESPACE}\" || true\n\necho \"--- Pod logs (if any) ---\"\nfor pod in $(\"${KUBECTL_BIN}\" get pods -n \"${MYNAMESPACE}\" -o jsonpath='{.items[*].metadata.name}' 2>/dev/null); do\n  echo \"=== Logs for pod: ${pod} ===\"\n  \"${KUBECTL_BIN}\" logs \"${pod}\" -n \"${MYNAMESPACE}\" --tail=50 || true\n  echo \"=== Events for pod: ${pod} ===\"\n  \"${KUBECTL_BIN}\" describe pod \"${pod}\" -n \"${MYNAMESPACE}\" | grep -A 20 \"^Events:\" || true\ndone\n\necho \"--- Events in namespace ${MYNAMESPACE} ---\"\n\"${KUBECTL_BIN}\" get events -n \"${MYNAMESPACE}\" --sort-by='.lastTimestamp' || true\n\necho \"=== END DEBUG ===\"\n\n\"${KIND_BIN}\" delete cluster -n \"${KIND_CLUSTER_NAME}\" || true\n\necho \"Deleting kind-registry\"\n\n\"${DOCKER_BIN}\" stop kind-registry || true\n\"${DOCKER_BIN}\" container rm kind-registry || true"
  },
  {
    "path": "e2e/util/test.sh",
    "content": "#!/usr/bin/env bash\n\n# --- begin runfiles.bash initialization v3 ---\n# Copy-pasted from the Bazel Bash runfiles library v3.\nset -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash\n# shellcheck disable=SC1090\nsource \"${RUNFILES_DIR:-/dev/null}/$f\" 2>/dev/null || \\\nsource \"$(grep -sm1 \"^$f \" \"${RUNFILES_MANIFEST_FILE:-/dev/null}\" | cut -f2- -d' ')\" 2>/dev/null || \\\nsource \"$0.runfiles/$f\" 2>/dev/null || \\\nsource \"$(grep -sm1 \"^$f \" \"$0.runfiles_manifest\" | cut -f2- -d' ')\" 2>/dev/null || \\\nsource \"$(grep -sm1 \"^$f \" \"$0.exe.runfiles_manifest\" | cut -f2- -d' ')\" 2>/dev/null || \\\n{ echo>&2 \"ERROR: cannot find $f\"; exit 1; }; f=; set -e\n# --- end runfiles.bash initialization v3 ---\n\n# Track overall exit code\nexit_code=0\n\n# Resolve teardown script path (needed for trap)\nteardown_script=\"\"\nif [[ -n \"${TEARDOWN:-}\" ]]; then\n  teardown_script=$(rlocation \"${TEARDOWN}\")\n  if [[ -z \"${teardown_script}\" || ! -f \"${teardown_script}\" ]]; then\n    echo >&2 \"ERROR: could not find TEARDOWN script at rlocationpath: ${TEARDOWN}\"\n    exit 1\n  fi\nfi\n\n# Teardown runs on exit, regardless of success or failure\ncleanup() {\n  if [[ -n \"${teardown_script}\" ]]; then\n    echo \"=== Running teardown: ${TEARDOWN} ===\"\n    \"${teardown_script}\" || true\n    echo \"=== Teardown completed ===\"\n  fi\n}\ntrap cleanup EXIT\n\n# Run setup if specified\nif [[ -n \"${SETUP:-}\" ]]; then\n  setup_script=$(rlocation \"${SETUP}\")\n  if [[ -z \"${setup_script}\" || ! -f \"${setup_script}\" ]]; then\n    echo >&2 \"ERROR: could not find SETUP script at rlocationpath: ${SETUP}\"\n    exit 1\n  fi\n\n  echo \"=== Running setup: ${SETUP} ===\"\n  if ! \"${setup_script}\"; then\n    echo >&2 \"=== Setup failed ===\"\n    exit 1\n  fi\n  echo \"=== Setup completed ===\"\nfi\n\n# Run all scripts passed as arguments\nfor rlocation_path in \"$@\"; do\n  script=$(rlocation \"${rlocation_path}\")\n  if [[ -z \"${script}\" || ! -f \"${script}\" ]]; then\n    echo >&2 \"ERROR: could not find script at rlocationpath: ${rlocation_path}\"\n    exit_code=1\n    break\n  fi\n\n  echo \"=== Running: ${rlocation_path} ===\"\n  if ! \"${script}\"; then\n    echo >&2 \"=== Failed: ${rlocation_path} ===\"\n    exit_code=1\n    break\n  fi\n  echo \"=== Completed: ${rlocation_path} ===\"\ndone\n\nif [[ ${exit_code} -eq 0 ]]; then\n  echo \"=== All scripts completed successfully ===\"\nfi\n\nexit ${exit_code}\n"
  },
  {
    "path": "e2e/util/util.bzl",
    "content": "\"\"\"Utility macros for e2e testing with kind clusters.\"\"\"\n\nload(\"@bazel_lib//lib:expand_template.bzl\", \"expand_template\")\nload(\"@rules_shell//shell:sh_binary.bzl\", \"sh_binary\")\nload(\"@rules_shell//shell:sh_test.bzl\", \"sh_test\")\n\ndef e2e_test(name, steps, **kwargs):\n    \"\"\"Declares an e2e test interacting with a kind cluster\n\n    Args:\n        name: name of the test target\n        steps: a list of labels producing binaries to run\n        **kwargs: additional options to pass to the underlying test rule\n    \"\"\"\n\n    sh_test(\n        name = \"manual_k8s\",\n        srcs = [\"//e2e/util:test.sh\"],\n        args = [\"$(rlocationpath {label})\".format(label = label) for label in steps],\n        data = [\n            \"//e2e/util:setup.sh\",\n            \"@io_k8s_sigs_kind//:kind\",\n            \"//kubectl:resolved_toolchain\",\n            \"//e2e/util:teardown.sh\",\n        ] + steps,\n        env = {\n            \"SETUP\": \"$(rlocationpath //e2e/util:setup.sh)\",\n            \"TEARDOWN\": \"$(rlocationpath //e2e/util:teardown.sh)\",\n            \"KIND_BIN_PATH\": \"$(rlocationpath @io_k8s_sigs_kind//:kind)\",\n            \"KUBECTL_BIN_PATH\": \"$(rlocationpath //kubectl:resolved_toolchain)\",\n        },\n        deps = [\n            \"@bazel_tools//tools/bash/runfiles\",\n        ],\n        **kwargs\n    )\n\ndef kubectl_cmd(name, args):\n    expand_template(\n        name = name + \".script\",\n        is_executable = True,\n        template = \"//e2e/util:kubectl_cmd.sh.tpl\",\n        substitutions = {\n            \"$$KUBECTL_ARGS$$\": \" \".join(args),\n        },\n        out = name + \".bash\",\n    )\n\n    sh_binary(\n        name = name,\n        srcs = [name + \".script\"],\n        env = {\n            \"KUBECTL_BIN_PATH\": \"$(rlocationpath //kubectl:resolved_toolchain)\",\n        },\n        data = [\n            \"//kubectl:resolved_toolchain\",\n        ],\n        deps = [\n            \"@bazel_tools//tools/bash/runfiles\",\n        ],\n        toolchains = [\n            \"//kubectl:resolved_toolchain\",\n        ],\n    )\n"
  },
  {
    "path": "examples/README.md",
    "content": "# Bazel GitOps Rules Examples\n\n## Overview\n\nExamples projects to demonstrate Bazel GitOps Rules in use:\n\n- [helloworld](./helloworld) -- a minimal Go application with `k8s_deploy` manifests\n- [legacy_docker](./legacy_docker/) -- a minimal container image built with rules_docker and adapted for use with rules_gitops\n\n\n\n"
  },
  {
    "path": "examples/helloworld/.bazelrc",
    "content": "common --lockfile_mode=off"
  },
  {
    "path": "examples/helloworld/.bazelversion",
    "content": "7.6.0\n"
  },
  {
    "path": "examples/helloworld/BUILD.bazel",
    "content": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License. You may obtain a copy\n# of the License at http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software distributed under\n# the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n# OF ANY KIND, either express or implied. See the License for the specific language\n# governing permissions and limitations under the License.\n\nload(\"@rules_gitops//adapters:rules_img.bzl\", \"k8s_push_info\")\nload(\"@rules_gitops//gitops:defs.bzl\", \"k8s_deploy\")\nload(\"@rules_gitops//kustomize:defs.bzl\", \"kustomization\")\nload(\"@rules_go//go:def.bzl\", \"go_binary\", \"go_library\", \"go_test\")\nload(\"@rules_img//img:image.bzl\", \"image_index\", \"image_manifest\")\nload(\"@rules_img//img:layer.bzl\", \"image_layer\")\nload(\"@rules_img//img:load.bzl\", \"image_load\")\nload(\"@rules_img//img:push.bzl\", \"image_push\")\n\nCLUSTER = \"kind-kind\"\n\nUSER = \"kind-kind\"\n\nREGISTRY = \"localhost:15000\"\n\nplatform(\n    name = \"linux_amd64\",\n    constraint_values = [\n        \"@platforms//os:linux\",\n        \"@platforms//cpu:x86_64\",\n    ],\n)\n\nplatform(\n    name = \"linux_arm64\",\n    constraint_values = [\n        \"@platforms//os:linux\",\n        \"@platforms//cpu:aarch64\",\n    ],\n)\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"helloworld.go\"],\n    importpath = \"github.com/adobe/rules_gitops/examples/helloworld\",\n    visibility = [\"//visibility:private\"],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    srcs = [\"helloworld_test.go\"],\n    embed = [\":go_default_library\"],\n)\n\ngo_binary(\n    name = \"helloworld\",\n    embed = [\":go_default_library\"],\n    visibility = [\"//visibility:public\"],\n)\n\nimage_layer(\n    name = \"go_layer\",\n    srcs = {\n        \"/bin/app\": \":helloworld\",\n    },\n)\n\nimage_manifest(\n    name = \"go_image\",\n    base = \"@alpine\",\n    entrypoint = [\"/bin/app\"],\n    layers = [\n        \":go_layer\",\n    ],\n    platform = \":linux_amd64\",\n)\n\nimage_index(\n    name = \"multiarch_image\",\n    manifests = [\":go_image\"],\n    platforms = [\n        \":linux_amd64\",\n        \":linux_arm64\",\n    ],\n)\n\nimage_load(\n    name = \"load\",\n    image = \":go_image\",\n    tag = \"helloworld:latest\",\n    visibility = [\"//:__pkg__\"],\n)\n\nimage_push(\n    name = \"push\",\n    image = \":go_image\",\n    registry = REGISTRY,\n    repository = \"rules_gitops/examples/helloworld\",\n    tag = \"native\",\n    visibility = [\"//:__pkg__\"],\n)\n\nimage_push(\n    name = \"push_multiarch\",\n    image = \":multiarch_image\",\n    registry = REGISTRY,\n    repository = \"rules_gitops/examples/helloworld\",\n    tag = \"native\",\n    visibility = [\"//:__pkg__\"],\n)\n\nk8s_push_info(\n    name = \"k8s_image\",\n    image = \":go_image\",\n    push = \":push\",\n    registry = REGISTRY,\n    repository = \"rules_gitops/examples/helloworld\",\n)\n\nk8s_push_info(\n    name = \"k8s_image_multiarch\",\n    image = \":multiarch_image\",\n    push = \":push_multiarch\",\n    registry = REGISTRY,\n    repository = \"rules_gitops/examples/helloworld\",\n)\n\nkustomization(\n    name = \"kustomize\",\n    testonly = True,\n    images = [\":k8s_image\"],\n    manifests = [\n        \"deployment.yaml\",\n    ],\n    namespace = \"\",\n)\n\nk8s_deploy(\n    name = \"mynamespace\",\n    cluster = CLUSTER,\n    images = [\":k8s_image\"],\n    manifests = [\n        \"deployment.yaml\",\n        \"service.yaml\",\n    ],\n    namespace = \"{BUILD_USER}\",\n    user = USER,\n)\n\n# NAMESPACE = \"hwteam\"\n\n# k8s_deploy(\n#     name = \"canary\",\n#     cluster = CLUSTER,\n#     deployment_branch = \"helloworld-canary\",\n#     image_digest_tag = True,  # test optional image tagging\n#     image_registry = REGISTRY,  # override the default registry for production\n#     image_repository_prefix = \"k8s\",\n#     images = {\n#         \"helloworld-image\": \":image\",\n#     },\n#     manifests = [\n#         \"deployment.yaml\",\n#         \"service.yaml\",\n#     ],\n#     name_suffix = \"-canary\",\n#     namespace = NAMESPACE,\n#     prefix_suffix_app_labels = True,\n#     user = USER,\n# )\n\n# k8s_deploy(\n#     name = \"release\",\n#     cluster = CLUSTER,\n#     deployment_branch = \"helloworld-prod\",\n#     image_digest_tag = True,  # test optional image tagging\n#     image_registry = REGISTRY,  # override the default registry host for production\n#     image_repository_prefix = \"k8s\",\n#     images = {\n#         \"helloworld-image\": \":image\",\n#     },\n#     manifests = [\n#         \"deployment.yaml\",\n#         \"service.yaml\",\n#     ],\n#     namespace = NAMESPACE,\n#     tags = [\"release\"],\n#     user = USER,\n# )\n\n# k8s_deploy(\n#     name = \"gitops_custom_path\",\n#     cluster = CLUSTER,\n#     deployment_branch = \"helloworld-gitops-custom-path\",\n#     gitops_path = \"custom_cloud\",\n#     image_digest_tag = True,  # test optional image tagging\n#     image_registry = REGISTRY,  # override the default registry host for production\n#     image_repository_prefix = \"k8s\",\n#     images = {\n#         \"helloworld-image\": \":image\",\n#     },\n#     manifests = [\n#         \"deployment.yaml\",\n#         \"service.yaml\",\n#     ],\n#     name_suffix = \"-gitops-custom-path\",\n#     namespace = NAMESPACE,\n#     user = USER,\n# )\n\n# sh_test(\n#     name = \"k8s_deploy_test\",\n#     srcs = [\"k8s_deploy_test.sh\"],\n#     args = [\n#         CLUSTER,\n#         NAMESPACE,\n#     ],\n#     data = [\n#         \":canary.show\",\n#         \":mynamespace.show\",\n#         \":release.show\",\n#     ],\n#     deps = [\n#         \"@bazel_tools//tools/bash/runfiles\",\n#     ],\n# )\n"
  },
  {
    "path": "examples/helloworld/MODULE.bazel",
    "content": "bazel_dep(name = \"platforms\", version = \"1.0.0\")\n\nbazel_dep(name = \"rules_gitops\", dev_dependency = True)\n\nkustomize = use_extension(\"@rules_gitops//kustomize:defs.bzl\", \"kustomize\")\nkustomize.toolchain(\n    version = \"5.7.1\",\n)\n\nlocal_path_override(\n    module_name = \"rules_gitops\",\n    path = \"../..\",\n)\n\nbazel_dep(name = \"rules_go\", version = \"0.52.0\")\n\nbazel_dep(name = \"rules_img\", version = \"0.3.3\", dev_dependency = True)\n\npull = use_repo_rule(\"@rules_img//img:pull.bzl\", \"pull\")\n\npull(\n    name = \"alpine\",\n    digest = \"sha256:51183f2cfa6320055da30872f211093f9ff1d3cf06f39a0bdb212314c5dc7375\",\n    layer_handling = \"lazy\",\n    registries = [\n        \"mirror.gcr.io\",\n        \"index.docker.io\",\n    ],\n    repository = \"library/alpine\",\n    tag = \"3.23\",\n)\n"
  },
  {
    "path": "examples/helloworld/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: helloworld\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: helloworld\n  template:\n    metadata:\n      labels:\n        app: helloworld\n    spec:\n      containers:\n        - name: helloworld\n          image: //:go_image\n          args:\n            - --port=8080\n          ports:\n            - containerPort: 8080\n              name: web\n          resources:\n            requests:\n              memory: 2Mi\n          readinessProbe:\n            httpGet:\n              path: /healthz\n              port: 8080\n          env:\n            - name: NODE_NAME\n              valueFrom:\n                fieldRef:\n                  fieldPath: spec.nodeName\n            - name: POD_NAME\n              valueFrom:\n                fieldRef:\n                  fieldPath: metadata.name\n            - name: POD_NAMESPACE\n              valueFrom:\n                fieldRef:\n                  fieldPath: metadata.namespace\n            - name: POD_IP\n              valueFrom:\n                fieldRef:\n                  fieldPath: status.podIP\n"
  },
  {
    "path": "examples/helloworld/helloworld.go",
    "content": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\npackage main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n)\n\nvar (\n\tport = flag.Int(\"port\", 8080, \"IP port\")\n)\n\nfunc printenv(w http.ResponseWriter, r *http.Request) {\n\tfor _, e := range os.Environ() {\n\t\tfmt.Fprintf(w, \"%s\\n\", e)\n\t}\n}\n\nfunc home(w http.ResponseWriter, r *http.Request) {\n\tio.WriteString(w, \"<html><body>Hello World!</body></html>\")\n}\n\nfunc main() {\n\tflag.Parse()\n\thttp.HandleFunc(\"/\", home)\n\thttp.HandleFunc(\"/healthz\", func(w http.ResponseWriter, r *http.Request) {\n\t\tfmt.Fprintf(w, \"ok\\n\")\n\t})\n\thttp.HandleFunc(\"/env\", printenv)\n\tfmt.Printf(\"Serving on port %d\\n\", *port)\n\tlog.Fatal(http.ListenAndServe(fmt.Sprintf(\":%d\", *port), nil))\n}\n"
  },
  {
    "path": "examples/helloworld/helloworld_test.go",
    "content": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\npackage main\n\nimport (\n\t\"io\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestHome(t *testing.T) {\n\treq := httptest.NewRequest(\"GET\", \"http://example.com/foo\", nil)\n\tw := httptest.NewRecorder()\n\thome(w, req)\n\n\tresp := w.Result()\n\tif resp.StatusCode != 200 {\n\t\tt.Fatalf(\"Unexpected status code %d, expectted 200\", resp.StatusCode)\n\t}\n\tbody, _ := io.ReadAll(resp.Body)\n\tif !strings.Contains(string(body), \"Hello World\") {\n\t\tt.Error(\"Unexpected content returned:\", string(body))\n\t}\n}\n"
  },
  {
    "path": "examples/helloworld/k8s_deploy_test.sh",
    "content": "#!/usr/bin/env bash\n\n# Debug\n# set -x\n# RUNFILES_LIB_DEBUG=1\n\n# --- begin runfiles.bash initialization v2 ---\n# Copy-pasted from the Bazel Bash runfiles library v2.\nset -uo pipefail\nf=bazel_tools/tools/bash/runfiles/runfiles.bash\nsource \"${RUNFILES_DIR:-/dev/null}/$f\" 2>/dev/null ||\n  source \"$(grep -sm1 \"^$f \" \"${RUNFILES_MANIFEST_FILE:-/dev/null}\" | cut -f2- -d' ')\" 2>/dev/null ||\n  source \"$0.runfiles/$f\" 2>/dev/null ||\n  source \"$(grep -sm1 \"^$f \" \"$0.runfiles_manifest\" | cut -f2- -d' ')\" 2>/dev/null ||\n  source \"$(grep -sm1 \"^$f \" \"$0.exe.runfiles_manifest\" | cut -f2- -d' ')\" 2>/dev/null ||\n  {\n    echo >&2 \"ERROR: cannot find $f\"\n    exit 1\n  }\nf=\nset -e\n# --- end runfiles.bash initialization v2 ---\n\nCLUSTER=\"$1\"\nNAMESPACE=\"$2\"\n\n$(rlocation examples/helloworld/mynamespace.show) > mynamespace.show\necho \"DEBUG: mynamespace.show:\"\ncat mynamespace.show\n\ngrep -F \"kind: Deployment\" mynamespace.show\ngrep -F \"kind: Service\" mynamespace.show\ngrep -F \"name: helloworld\" mynamespace.show\ngrep -E \"image: localhost:15000/.*/helloworld/image@sha256\" mynamespace.show\ngrep -E \"app_label_image_short_digest\" mynamespace.show | grep -v -F 'image.short-digest'\n\n$(rlocation examples/helloworld/canary.show) > canary.show\necho \"DEBUG: canary.show:\"\ncat canary.show\n\ngrep -F \"kind: Deployment\" canary.show\ngrep -F \"kind: Service\" canary.show\ngrep -F \"namespace: $NAMESPACE\" canary.show\ngrep -F \"name: helloworld-canary\" canary.show\ngrep -E \"image: localhost:15000/k8s/helloworld/image@sha256\" canary.show\n\n$(rlocation examples/helloworld/release.show) > release.show\necho \"DEBUG: release.show:\"\ncat release.show\n\ngrep -F \"kind: Deployment\" release.show\ngrep -F \"kind: Service\" release.show\ngrep -F \"namespace: $NAMESPACE\" canary.show\ngrep -F \"name: helloworld\" mynamespace.show\ngrep -E \"image: localhost:15000/k8s/helloworld/image@sha256\" release.show\n"
  },
  {
    "path": "examples/helloworld/service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: helloworld\n  labels:\n    app: helloworld\nspec:\n  ports:\n    - port: 80\n      name: web\n      targetPort: 8080\n  selector:\n    app: helloworld\n"
  },
  {
    "path": "examples/legacy_docker/.bazelrc",
    "content": "common --lockfile_mode=off\ncommon --@io_bazel_rules_docker//transitions:enable=false"
  },
  {
    "path": "examples/legacy_docker/.bazelversion",
    "content": "7.6.0\n"
  },
  {
    "path": "examples/legacy_docker/BUILD.bazel",
    "content": "load(\n    \"@io_bazel_rules_docker//container:container.bzl\",\n    \"container_image\",\n    \"container_push\",\n)\nload(\n    \"@rules_gitops//gitops:defs.bzl\",\n    \"k8s_deploy\",\n)\nload(\n    \"//adapters:docker.bzl\",\n    \"k8s_push_info\",\n)\n\nCLUSTER = \"kind-kind\"\n\nUSER = \"kind-kind\"\n\nREGISTRY = \"localhost:15000\"\n\nplatform(\n    name = \"linux_amd64\",\n    constraint_values = [\n        \"@platforms//os:linux\",\n        \"@platforms//cpu:x86_64\",\n    ],\n)\n\ncontainer_image(\n    name = \"image\",\n    base = \"@alpine_linux_amd64//image\",\n    cmd = [\n        \"/bin/sleep\",\n        \"10\",\n    ],\n    files = [\":container_contents.txt\"],\n)\n\ngenrule(\n    name = \"image_digestsha256\",\n    srcs = [\":image.digest\"],\n    outs = [\"image.digestsha256\"],\n    cmd = \"sed 's/sha256://' $< > $@\",\n)\n\ncontainer_push(\n    name = \"push\",\n    format = \"Docker\",\n    image = \"image\",\n    registry = REGISTRY,\n    repository = \"rules_gitops/examples/legacy_docker/image\",\n    tag_file = \"image.digestsha256\",\n)\n\nk8s_push_info(\n    name = \"k8s_image\",\n    image = \"image\",\n    push = \"push\",\n    registry = REGISTRY,\n    repository = \"rules_gitops/examples/legacy_docker/image\",\n)\n\nk8s_deploy(\n    name = \"mynamespace\",\n    cluster = CLUSTER,\n    gitops = True,\n    images = [\":k8s_image\"],\n    manifests = [\n        \"deployment.yaml\",\n    ],\n    namespace = \"hwteam\",\n    user = USER,\n)\n"
  },
  {
    "path": "examples/legacy_docker/MODULE.bazel",
    "content": "bazel_dep(name = \"rules_gitops\", dev_dependency = True)\n\nkustomize = use_extension(\"@rules_gitops//kustomize:defs.bzl\", \"kustomize\")\nkustomize.toolchain(\n    version = \"5.7.1\",\n)\n\nlocal_path_override(\n    module_name = \"rules_gitops\",\n    path = \"../..\",\n)\n"
  },
  {
    "path": "examples/legacy_docker/WORKSPACE",
    "content": "workspace(\n    name = \"hzrepo\",\n)\n\nload(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_archive\")\n\n###############################\n# Golang\n###############################\n\nhttp_archive(\n    name = \"io_bazel_rules_go\",\n    sha256 = \"0936c9bc3c4321ee372cb8f66dd972d368cb940ed01a9ba9fd7debcf0093f09b\",\n    urls = [\n        \"https://github.com/bazel-contrib/rules_go/releases/download/v0.51.0/rules_go-v0.51.0.zip\",\n        \"https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.51.0/rules_go-v0.51.0.zip\",\n    ],\n)\n\nload(\"@io_bazel_rules_go//go:deps.bzl\", \"go_download_sdk\", \"go_register_toolchains\", \"go_rules_dependencies\")\n\n# Download Go SDK\ngo_download_sdk(\n    name = \"go_sdk\",\n    version = \"1.23.4\",\n)\n\ngo_rules_dependencies()\n\ngo_register_toolchains()\n\nhttp_archive(\n    name = \"bazel_gazelle\",\n    integrity = \"sha256-qAiTKSrh146u7dUNHKuY8kKhfj1XQbG5+1i1/Z0tV7w=\",\n    urls = [\n        \"https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.40.0/bazel-gazelle-v0.40.0.tar.gz\",\n        \"https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.40.0/bazel-gazelle-v0.40.0.tar.gz\",\n    ],\n)\n\n###############################\n# rules_docker\n###############################\n\nhttp_archive(\n    name = \"io_bazel_rules_docker\",\n    patch_args = [\"-p1\"],\n    # https://github.com/bazelbuild/rules_docker/pull/2068#issuecomment-1242158255\n    patches = [\"//:patches/docker.patch\"],\n    sha256 = \"f6dcb97e992f13bc9effd794e9bb300f06b0dadc88061f81ae68d8d5994be964\",\n    urls = [\n        \"https://mirror.bazel.build/github.com/bazelbuild/rules_docker/releases/download/v0.26.0/rules_docker-v0.26.0.tar.gz\",\n        \"https://github.com/bazelbuild/rules_docker/releases/download/v0.26.0/rules_docker-v0.26.0.tar.gz\",\n    ],\n)\n\nload(\"@io_bazel_rules_docker//repositories:repositories.bzl\", container_repositories = \"repositories\")\n\ncontainer_repositories()\n\nload(\"@io_bazel_rules_docker//repositories:deps.bzl\", container_deps = \"deps\")\n\ncontainer_deps()\n\nload(\n    \"@io_bazel_rules_docker//container:container.bzl\",\n    \"container_pull\",\n)\n\ncontainer_pull(\n    name = \"alpine_linux_amd64\",\n    digest = \"sha256:954b378c375d852eb3c63ab88978f640b4348b01c1b3456a024a81536dafbbf4\",\n    registry = \"index.docker.io\",\n    repository = \"library/alpine\",\n    tag = \"3.8\",\n)\n"
  },
  {
    "path": "examples/legacy_docker/adapters/BUILD.bazel",
    "content": ""
  },
  {
    "path": "examples/legacy_docker/adapters/docker.bzl",
    "content": "\"\"\"Adapter for legacy io_bazel_rules_docker container images.\"\"\"\n\nload(\"@io_bazel_rules_docker//container:providers.bzl\", \"ImageInfo\", \"PushInfo\")\nload(\"@rules_gitops//adapters:providers.bzl\", \"K8sPushInfo\")\n\ndef _k8s_push_info_impl(ctx):\n    digestfile = ctx.attr.image[ImageInfo].container_parts[\"digest\"]\n\n    return [\n        DefaultInfo(),\n        K8sPushInfo(\n            image_label = ctx.attr.image.label,\n            registry = ctx.attr.registry,\n            repository = ctx.attr.repository,\n            digestfile = digestfile,\n            pusher = ctx.attr.push[DefaultInfo],\n        ),\n    ]\n\nk8s_push_info = rule(\n    implementation = _k8s_push_info_impl,\n    attrs = {\n        \"image\": attr.label(mandatory = True, providers = [ImageInfo]),\n        \"push\": attr.label(mandatory = True, cfg = \"target\", providers = [PushInfo]),\n        \"registry\": attr.string(mandatory = True),\n        \"repository\": attr.string(mandatory = True),\n    },\n)\n"
  },
  {
    "path": "examples/legacy_docker/container_contents.txt",
    "content": "Hello rules_docker!"
  },
  {
    "path": "examples/legacy_docker/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: legacydocker\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: legacydocker\n  template:\n    metadata:\n      labels:\n        app: legacydocker\n    spec:\n      containers:\n        - name: legacydocker\n          image: //:image"
  },
  {
    "path": "examples/legacy_docker/patches/docker.patch",
    "content": "diff --git a/container/image.bzl b/container/image.bzl\nindex ceeea01..f2762d4 100644\n--- a/container/image.bzl\n+++ b/container/image.bzl\n@@ -769,11 +769,7 @@ _outputs[\"build_script\"] = \"%{name}.executable\"\n def _image_transition_impl(settings, attr):\n     if not settings[\"@io_bazel_rules_docker//transitions:enable\"]:\n         # Once bazel < 5.0 is not supported we can return an empty dict here\n-        return {\n-            \"//command_line_option:platforms\": settings[\"//command_line_option:platforms\"],\n-            \"@io_bazel_rules_docker//platforms:image_transition_cpu\": \"//plaftorms:image_transition_cpu_unset\",\n-            \"@io_bazel_rules_docker//platforms:image_transition_os\": \"//plaftorms:image_transition_os_unset\",\n-        }\n+        return {}\n \n     return {\n         \"//command_line_option:platforms\": \"@io_bazel_rules_docker//platforms:image_transition\",\n"
  },
  {
    "path": "gitops/BUILD.bazel",
    "content": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\n\n# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License. You may obtain a copy\n# of the License at http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software distributed under\n# the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n# OF ANY KIND, either express or implied. See the License for the specific language\n# governing permissions and limitations under the License.\n\nlicenses([\"notice\"])  # Apache 2.0\n\npackage(default_visibility = [\"//visibility:public\"])\n\nalias(\n    name = \"nameprefix_deployment_labels_config.yaml\",\n    actual = \"//gitops/private:nameprefix_deployment_labels_config.yaml\",\n)\n\nalias(\n    name = \"namesuffix_deployment_labels_config.yaml\",\n    actual = \"//gitops/private:namesuffix_deployment_labels_config.yaml\",\n)\n\nbzl_library(\n    name = \"defs\",\n    srcs = [\"defs.bzl\"],\n    deps = [\n        \"//gitops/private:gitops\",\n        \"//gitops/private:k8s_deploy\",\n    ],\n)\n"
  },
  {
    "path": "gitops/analysis/BUILD.bazel",
    "content": "load(\"@protobuf//bazel:proto_library.bzl\", \"proto_library\")\nload(\"@rules_go//go:def.bzl\", \"go_library\")\nload(\"@rules_go//proto:def.bzl\", \"go_proto_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"analysis.go\"],\n    embed = [\":analysis_go_proto\"],\n    importpath = \"github.com/adobe/rules_gitops/gitops/analysis\",\n    visibility = [\"//visibility:public\"],\n)\n\nproto_library(\n    name = \"analysis_proto\",\n    srcs = [\"analysis.proto\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\"//gitops/blaze_query:blaze_query_proto\"],\n)\n\ngo_proto_library(\n    name = \"analysis_go_proto\",\n    importpath = \"github.com/adobe/rules_gitops/gitops/analysis\",\n    proto = \":analysis_proto\",\n    visibility = [\"//visibility:public\"],\n    deps = [\"//gitops/blaze_query:go_default_library\"],\n)\n"
  },
  {
    "path": "gitops/analysis/analysis.go",
    "content": "package analysis"
  },
  {
    "path": "gitops/analysis/analysis.proto",
    "content": "// Copyright 2018 The Bazel Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//    http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nsyntax = \"proto3\";\n\npackage analysis;\n\noption java_package = \"com.google.devtools.build.lib.analysis\";\noption java_outer_classname = \"AnalysisProtos\";\n\nimport \"gitops/blaze_query/build.proto\";\n\n// Container for the action graph properties.\nmessage ActionGraphContainer {\n  repeated Artifact artifacts = 1;\n  repeated Action actions = 2;\n  repeated Target targets = 3;\n  repeated DepSetOfFiles dep_set_of_files = 4;\n  repeated Configuration configuration = 5;\n  repeated AspectDescriptor aspect_descriptors = 6;\n  repeated RuleClass rule_classes = 7;\n}\n\n// Represents a single artifact, whether it's a source file or a derived output\n// file.\nmessage Artifact {\n  // Identifier for this artifact; this is an opaque string, only valid for this\n  // particular dump of the analysis.\n  string id = 1;\n\n  // The relative path of the file within the execution root.\n  string exec_path = 2;\n\n  // True iff the artifact is a tree artifact, i.e. the above exec_path refers\n  // a directory.\n  bool is_tree_artifact = 3;\n}\n\n// Represents a single action, which is a function from Artifact(s) to\n// Artifact(s).\nmessage Action {\n  // The target that was responsible for the creation of the action.\n  string target_id = 1;\n\n  // The aspects that were responsible for the creation of the action (if any).\n  // In the case of aspect-on-aspect, AspectDescriptors are listed in\n  // topological order of the dependency graph.\n  // e.g. [A, B] would imply that aspect A is applied on top of aspect B.\n  repeated string aspect_descriptor_ids = 2;\n\n  // Encodes all significant behavior that might affect the output. The key\n  // must change if the work performed by the execution of this action changes.\n  // Note that the key doesn't include checksums of the input files.\n  string action_key = 3;\n\n  // The mnemonic for this kind of action.\n  string mnemonic = 4;\n\n  // The configuration under which this action is executed.\n  string configuration_id = 5;\n\n  // The command line arguments of the action. This will be only set if\n  // explicitly requested.\n  repeated string arguments = 6;\n\n  // The list of environment variables to be set before executing the command.\n  repeated KeyValuePair environment_variables = 7;\n\n  // The set of input dep sets that the action depends upon. If the action does\n  // input discovery, the contents of this set might change during execution.\n  repeated string input_dep_set_ids = 8;\n\n  // The list of Artifact IDs that represent the output files that this action\n  // will generate.\n  repeated string output_ids = 9;\n\n  // True iff the action does input discovery during execution.\n  bool discovers_inputs = 10;\n\n  // Execution info for the action.  Remote execution services may use this\n  // information to modify the execution environment, but actions will\n  // generally not be aware of it.\n  repeated KeyValuePair execution_info = 11;\n\n  // The list of param files. This will be only set if explicitly requested.\n  repeated ParamFile param_files = 12;\n}\n\n// Represents a single target (without configuration information) that is\n// associated with an action.\nmessage Target {\n  // Identifier for this target; this is an opaque string, only valid for this\n  // particular dump of the analysis.\n  string id = 1;\n\n  // Label of the target, e.g. //foo:bar.\n  string label = 2;\n\n  // Class of the rule.\n  string rule_class_id = 3;\n}\n\nmessage RuleClass {\n  // Identifier for this rule class; this is an opaque string, only valid for\n  // this particular dump of the analysis.\n  string id = 1;\n\n  // Name of the rule class, e.g. cc_library.\n  string name = 2;\n}\n\n// Represents an invocation specific descriptor of an aspect.\nmessage AspectDescriptor {\n  // Identifier for this aspect descriptor; this is an opaque string, only valid\n  // for the particular dump of the analysis.\n  string id = 1;\n\n  // The name of the corresponding aspect. For native aspects, it's the Java\n  // class name, for Skylark aspects it's the bzl file followed by a % sign\n  // followed by the name of the aspect.\n  string name = 2;\n\n  // The list of parameters bound to a particular invocation of that aspect on\n  // a target. Note that aspects can be executed multiple times on the same\n  // target in different order.\n  repeated KeyValuePair parameters = 3;\n}\n\nmessage DepSetOfFiles {\n  // Identifier for this named set of files; this is an opaque string, only\n  // valid for the particular dump of the analysis.\n  string id = 1;\n\n  // Other transitively included named set of files.\n  repeated string transitive_dep_set_ids = 2;\n\n  // The list of input artifact IDs that are immediately contained in this set.\n  repeated string direct_artifact_ids = 3;\n}\n\nmessage Configuration {\n  // Identifier for this configuration; this is an opaque string, only valid for\n  // the particular dump of the analysis.\n  string id = 1;\n\n  // The mnemonic representing the build configuration.\n  string mnemonic = 2;\n\n  // The platform string.\n  string platform_name = 3;\n\n  // The checksum representation of the configuration options;\n  string checksum = 4;\n}\n\nmessage KeyValuePair {\n  // The variable name.\n  string key = 1;\n\n  // The variable value.\n  string value = 2;\n}\n\nmessage ConfiguredTarget {\n  // The target. We use blaze_query.Target defined in build.proto instead of\n  // the Target defined in this file because blaze_query.Target is much heavier\n  // and will output proto results similar to what users are familiar with from\n  // regular blaze query.\n  blaze_query.Target target = 1;\n\n  // The configuration\n  Configuration configuration = 2;\n}\n\n// Container for cquery results\nmessage CqueryResult {\n  // All the configuredtargets returns by cquery\n  repeated ConfiguredTarget results = 1;\n}\n\n// Content of a param file.\nmessage ParamFile {\n  // The exec path of the param file artifact.\n  string exec_path = 1;\n\n  // The arguments in the param file.\n  // Each argument corresponds to a line in the param file.\n  repeated string arguments = 2;\n}\n"
  },
  {
    "path": "gitops/bazel/BUILD.bazel",
    "content": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License. You may obtain a copy\n# of the License at http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software distributed under\n# the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n# OF ANY KIND, either express or implied. See the License for the specific language\n# governing permissions and limitations under the License.\n\nload(\"@rules_go//go:def.bzl\", \"go_library\", \"go_test\")\n\nlicenses([\"notice\"])  # Apache 2.0\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"bazeltargets.go\"],\n    importpath = \"github.com/adobe/rules_gitops/gitops/bazel\",\n    visibility = [\"//visibility:public\"],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    srcs = [\"bazeltargets_test.go\"],\n    embed = [\":go_default_library\"],\n)\n"
  },
  {
    "path": "gitops/bazel/bazeltargets.go",
    "content": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\npackage bazel\n\nimport \"strings\"\n\n// TargetToExecutable converts bazel target name to respective executable name in bazel-bin\nfunc TargetToExecutable(target string) string {\n\tif !strings.HasPrefix(target, \"//\") {\n\t\treturn target\n\t}\n\ttarget = \"bazel-bin/\" + target[2:]\n\ttarget = strings.Replace(target, \":\", \"/\", 1)\n\treturn target\n}\n"
  },
  {
    "path": "gitops/bazel/bazeltargets_test.go",
    "content": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\npackage bazel\n\nimport \"testing\"\n\nfunc TestTargetToExecutableHappypath(t *testing.T) {\n\ts := TargetToExecutable(\"//rtb/bidder:rtb-uat-k8s01-iad-1b-bidder-first-uat.gitops\")\n\tif s != \"bazel-bin/rtb/bidder/rtb-uat-k8s01-iad-1b-bidder-first-uat.gitops\" {\n\t\tt.Error(\"unexpected result\", s)\n\t}\n}\n"
  },
  {
    "path": "gitops/blaze_query/BUILD.bazel",
    "content": "load(\"@protobuf//bazel:proto_library.bzl\", \"proto_library\")\nload(\"@rules_go//proto:def.bzl\", \"go_proto_library\")\n\n# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License. You may obtain a copy\n# of the License at http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software distributed under\n# the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n# OF ANY KIND, either express or implied. See the License for the specific language\n# governing permissions and limitations under the License.\n\nload(\"@rules_go//go:def.bzl\", \"go_library\")\n\nlicenses([\"notice\"])  # Apache 2.0\n\ngo_library(\n    name = \"go_default_library\",\n    embed = [\":blaze_query_go_proto\"],\n    importpath = \"github.com/adobe/rules_gitops/gitops/blaze_query\",\n    visibility = [\"//visibility:public\"],\n)\n\nproto_library(\n    name = \"blaze_query_proto\",\n    srcs = [\"build.proto\"],\n    visibility = [\"//visibility:public\"],\n)\n\ngo_proto_library(\n    name = \"blaze_query_go_proto\",\n    importpath = \"github.com/adobe/rules_gitops/gitops/blaze_query\",\n    proto = \":blaze_query_proto\",\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "gitops/blaze_query/build.proto",
    "content": "// Copyright 2014 The Bazel Authors. All rights reserved.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//    http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n// This file contains the protocol buffer representation of a build\n// file or 'blaze query --output=proto' call.\n\nsyntax = \"proto2\";\n\npackage blaze_query;\n\n// option cc_api_version = 2;\n// option java_api_version = 1;\n\noption java_package = \"com.google.devtools.build.lib.query2.proto.proto2api\";\n\nmessage License {\n  repeated string license_type = 1;\n  repeated string exception = 2;\n}\n\nmessage StringDictEntry {\n  required string key = 1;\n  required string value = 2;\n}\n\nmessage LabelDictUnaryEntry {\n  required string key = 1;\n  required string value = 2;\n}\n\nmessage LabelListDictEntry {\n  required string key = 1;\n  repeated string value = 2;\n}\n\nmessage LabelKeyedStringDictEntry {\n  required string key = 1;\n  required string value = 2;\n}\n\nmessage StringListDictEntry {\n  required string key = 1;\n  repeated string value = 2;\n}\n\n// Represents an entry attribute of a Fileset rule in a build file.\nmessage FilesetEntry {\n  // Indicates what to do when a source file is actually a symlink.\n  enum SymlinkBehavior {\n    COPY = 1;\n    DEREFERENCE = 2;\n  }\n\n  // The label pointing to the source target where files are copied from.\n  required string source = 1;\n\n  // The relative path within the fileset rule where files will be mapped.\n  required string destination_directory = 2;\n\n  // Whether the files= attribute was specified. This is necessary because\n  // no files= attribute and files=[] mean different things.\n  optional bool files_present = 7;\n\n  // A list of file labels to include from the source directory.\n  repeated string file = 3;\n\n  // If this is a fileset entry representing files within the rule\n  // package, this lists relative paths to files that should be excluded from\n  // the set.  This cannot contain values if 'file' also has values.\n  repeated string exclude = 4;\n\n  // This field is optional because there will be some time when the new\n  // PB is used by tools depending on blaze query, but the new blaze version\n  // is not yet released.\n  // TODO(bazel-team): Make this field required once a version of Blaze is\n  // released that outputs this field.\n  optional SymlinkBehavior symlink_behavior = 5 [ default=COPY ];\n\n  // The prefix to strip from the path of the files in this FilesetEntry. Note\n  // that no value and the empty string as the value mean different things here.\n  optional string strip_prefix = 6;\n}\n\n// A rule attribute. Each attribute must have a type and one of the various\n// value fields populated - for the most part.\n//\n// Attributes of BOOLEAN and TRISTATE type may set all of the int, bool, and\n// string values for backwards compatibility with clients that expect them to\n// be set.\n//\n// Attributes of INTEGER, STRING, LABEL, LICENSE, BOOLEAN, and TRISTATE type\n// may set *none* of the values. This can happen if the Attribute message is\n// prepared for a client that doesn't support SELECTOR_LIST, but the rule has\n// a selector list value for the attribute. (Selector lists for attributes of\n// other types--the collection types--are handled differently when prepared\n// for such a client. The possible collection values are gathered together\n// and flattened.)\n//\n// By checking the type, the appropriate value can be extracted - see the\n// comments on each type for the associated value.  The order of lists comes\n// from the blaze parsing. If an attribute is of a list type, the associated\n// list should never be empty.\nmessage Attribute {\n  reserved 12, 16;\n\n  // Indicates the type of attribute.\n  enum Discriminator {\n    INTEGER = 1;             // int_value\n    STRING = 2;              // string_value\n    LABEL = 3;               // string_value\n    OUTPUT = 4;              // string_value\n    STRING_LIST = 5;         // string_list_value\n    LABEL_LIST = 6;          // string_list_value\n    OUTPUT_LIST = 7;         // string_list_value\n    DISTRIBUTION_SET = 8;    // string_list_value - order is unimportant\n    LICENSE = 9;             // license\n    STRING_DICT = 10;        // string_dict_value\n    FILESET_ENTRY_LIST = 11; // fileset_list_value\n    LABEL_LIST_DICT = 12;    // label_list_dict_value\n    STRING_LIST_DICT = 13;   // string_list_dict_value\n    BOOLEAN = 14;            // int, bool and string value\n    TRISTATE = 15;           // tristate, int and string value\n    INTEGER_LIST = 16;       // int_list_value\n    UNKNOWN = 18;            // unknown type, use only for build extensions\n    LABEL_DICT_UNARY = 19;   // label_dict_unary_value\n    SELECTOR_LIST = 20;      // selector_list\n    LABEL_KEYED_STRING_DICT = 21; // label_keyed_string_dict\n\n    DEPRECATED_STRING_DICT_UNARY = 17;\n\n  }\n\n  // Values for the TriState field type.\n  enum Tristate {\n    NO = 0;\n    YES = 1;\n    AUTO = 2;\n  }\n\n  message SelectorEntry {\n    reserved 12;\n\n    // The key of the selector entry. At this time, this is the label of a\n    // config_setting rule, or the pseudo-label \"//conditions:default\".\n    optional string label = 1;\n\n    // True if the entry's value is the default value for the type as a\n    // result of the condition value being specified as None (ie:\n    // {\"//condition\": None}).\n    optional bool is_default_value = 16;\n\n    // Exactly one of the following fields (except for glob_criteria) must be\n    // populated - note that the BOOLEAN and TRISTATE caveat in Attribute's\n    // comment does not apply here. The type field in the SelectorList\n    // containing this entry indicates which of these fields is populated,\n    // in accordance with the comments on Discriminator enum values above.\n    // (To be explicit: BOOLEAN populates the boolean_value field and TRISTATE\n    // populates the tristate_value field.)\n    optional int32 int_value = 2;\n    optional string string_value = 3;\n    optional bool boolean_value = 4;\n    optional Tristate tristate_value = 5;\n    repeated string string_list_value = 6;\n    optional License license = 7;\n    repeated StringDictEntry string_dict_value = 8;\n    repeated FilesetEntry fileset_list_value = 9;\n    repeated LabelListDictEntry label_list_dict_value = 10;\n    repeated StringListDictEntry string_list_dict_value = 11;\n    repeated int32 int_list_value = 13;\n    repeated LabelDictUnaryEntry label_dict_unary_value = 15;\n    repeated LabelKeyedStringDictEntry label_keyed_string_dict_value = 17;\n\n    repeated bytes DEPRECATED_string_dict_unary_value = 14;\n  }\n\n  message Selector {\n    // The list of (label, value) pairs in the map that defines the selector.\n    // At this time, this cannot be empty, i.e. a selector has at least one\n    // entry.\n    repeated SelectorEntry entries = 1;\n\n    // Whether or not this has any default values.\n    optional bool has_default_value = 2;\n\n    // The error message when no condition matches.\n    optional string no_match_error = 3;\n  }\n\n  message SelectorList {\n    // The type that this selector list evaluates to, and the type that each\n    // selector in the list evaluates to. At this time, this cannot be\n    // SELECTOR_LIST, i.e. selector lists do not nest.\n    optional Discriminator type = 1;\n\n    // The list of selector elements in this selector list. At this time, this\n    // cannot be empty, i.e. a selector list is never empty.\n    repeated Selector elements = 2;\n  }\n\n  // The name of the attribute\n  required string name = 1;\n\n  // Whether the attribute was explicitly specified\n  optional bool explicitly_specified = 13;\n\n  // If this attribute has a string value or a string list value, then this\n  // may be set to indicate that the value may be treated as a label that\n  // isn't a dependency of this attribute's rule.\n  optional bool nodep = 20;\n\n  // The type of attribute.  This message is used for all of the different\n  // attribute types so the discriminator helps for figuring out what is\n  // stored in the message.\n  required Discriminator type = 2;\n\n  // If this attribute has an integer value this will be populated.\n  // Boolean and TriState also use this field as [0,1] and [-1,0,1]\n  // for [false, true] and [auto, no, yes] respectively.\n  optional int32 int_value = 3;\n\n  // If the attribute has a string value this will be populated.  Label and\n  // path attributes use this field as the value even though the type may\n  // be LABEL or something else other than STRING.\n  optional string string_value = 5;\n\n  // If the attribute has a boolean value this will be populated.\n  optional bool boolean_value = 14;\n\n  // If the attribute is a Tristate value, this will be populated.\n  optional Tristate tristate_value = 15;\n\n  // The value of the attribute has a list of string values (label and path\n  // note from STRING applies here as well).\n  repeated string string_list_value = 6;\n\n  // If this is a license attribute, the license information is stored here.\n  optional License license = 7;\n\n  // If this is a string dict, each entry will be stored here.\n  repeated StringDictEntry string_dict_value = 8;\n\n  // If the attribute is part of a Fileset, the fileset entries are stored in\n  // this field.\n  repeated FilesetEntry fileset_list_value = 9;\n\n  // If this is a label list dict, each entry will be stored here.\n  repeated LabelListDictEntry label_list_dict_value = 10;\n\n  // If this is a string list dict, each entry will be stored here.\n  repeated StringListDictEntry string_list_dict_value = 11;\n\n  // The value of the attribute has a list of int32 values\n  repeated int32 int_list_value = 17;\n\n  // If this is a label dict unary, each entry will be stored here.\n  repeated LabelDictUnaryEntry label_dict_unary_value = 19;\n\n  // If this is a label-keyed string dict, each entry will be stored here.\n  repeated LabelKeyedStringDictEntry label_keyed_string_dict_value = 22;\n\n  // If this attribute's value is an expression containing one or more select\n  // expressions, then its type is SELECTOR_LIST and a SelectorList will be\n  // stored here.\n  optional SelectorList selector_list = 21;\n\n  repeated bytes DEPRECATED_string_dict_unary_value = 18;\n}\n\n// A rule instance (e.g., cc_library foo, java_binary bar).\nmessage Rule {\n  reserved 8, 11;\n\n  // The name of the rule (formatted as an absolute label, e.g. //foo/bar:baz).\n  required string name = 1;\n\n  // The rule class (e.g., java_library)\n  required string rule_class = 2;\n\n  // The BUILD file and line number of the location (formatted as\n  // <absolute_path>:<line_number>) in the rule's package's BUILD file where the\n  // rule instance was instantiated. The line number will be that of a rule\n  // invocation or macro call (that in turn invoked a rule). See\n  // https://docs.bazel.build/versions/master/skylark/macros.html#macro-creation\n  optional string location = 3;\n\n  // All of the attributes that describe the rule.\n  repeated Attribute attribute = 4;\n\n  // All of the inputs to the rule (formatted as absolute labels). These are\n  // predecessors in the dependency graph.\n  repeated string rule_input = 5;\n\n  // All of the outputs of the rule (formatted as absolute labels). These are\n  // successors in the dependency graph.\n  repeated string rule_output = 6;\n\n  // The set of all \"features\" inherited from the rule's package declaration.\n  repeated string default_setting = 7;\n\n  // The rule's class's public by default value.\n  optional bool public_by_default = 9;\n\n  // If this rule is of a skylark-defined RuleClass.\n  optional bool is_skylark = 10;\n\n  // Hash encapsulating the behavior of this Skylark rule. Any change to this\n  // rule's definition that could change its behavior will be reflected here.\n  optional string skylark_environment_hash_code = 12;\n}\n\n// Summary of all transitive dependencies of 'rule,' where each dependent\n// rule is included only once in the 'dependency' field.  Gives complete\n// information to analyze the single build target labeled rule.name,\n// including optional location of target in BUILD file.\nmessage RuleSummary {\n  required Rule rule = 1;\n  repeated Rule dependency = 2;\n  optional string location = 3;\n}\n\n// A package group. Aside from the name, it contains the list of packages\n// present in the group (as specified in the BUILD file).\nmessage PackageGroup {\n  reserved 4;\n\n  // The name of the package group\n  required string name = 1;\n\n  // The list of packages as specified in the BUILD file. Currently this is\n  // only a list of packages, but some time in the future, there might be\n  // some type of wildcard mechanism.\n  repeated string contained_package = 2;\n\n  // The list of sub package groups included in this one.\n  repeated string included_package_group = 3;\n}\n\n// An environment group.\nmessage EnvironmentGroup {\n  // The name of the environment group.\n  required string name = 1;\n\n  // The environments that belong to this group (as labels).\n  repeated string environment = 2;\n\n  // The member environments that rules implicitly support if not otherwise\n  // specified.\n  repeated string default = 3;\n}\n\n// A file that is an input into the build system.\n// Next-Id: 10\nmessage SourceFile {\n  reserved 7;\n\n  // The name of the source file (a label).\n  required string name = 1;\n\n  // The location of the source file.  This is a path with line numbers, not\n  // a label in the build system.\n  optional string location = 2;\n\n  // Labels of .bzl (Skylark) files that are transitively loaded in this BUILD\n  // file. This is present only when the SourceFile represents a BUILD file that\n  // loaded .bzl files.\n  // TODO(bazel-team): Rename this field.\n  repeated string subinclude = 3;\n\n  // Labels of package groups that are mentioned in the visibility declaration\n  // for this source file.\n  repeated string package_group = 4;\n\n  // Labels mentioned in the visibility declaration (including :__pkg__ and\n  // //visibility: ones)\n  repeated string visibility_label = 5;\n\n  // The package-level features enabled for this package. Only present if the\n  // SourceFile represents a BUILD file.\n  repeated string feature = 6;\n\n  // License attribute for the file.\n  optional License license = 8;\n\n  // True if the package contains an error. Only present if the SourceFile\n  // represents a BUILD file.\n  optional bool package_contains_errors = 9;\n}\n\n// A file that is the output of a build rule.\nmessage GeneratedFile {\n  // The name of the generated file (a label).\n  required string name = 1;\n\n  // The label of the target that generates the file.\n  required string generating_rule = 2;\n\n  // The path of the output file (not a label).\n  optional string location = 3;\n}\n\n// A target from a blaze query execution.  Similar to the Attribute message,\n// the Discriminator is used to determine which field contains information.\n// For any given type, only one of these can be populated in a single Target.\nmessage Target {\n  enum Discriminator {\n    RULE = 1;\n    SOURCE_FILE = 2;\n    GENERATED_FILE = 3;\n    PACKAGE_GROUP = 4;\n    ENVIRONMENT_GROUP = 5;\n  }\n\n  // The type of target contained in the message.\n  required Discriminator type = 1;\n\n  // If this target represents a rule, the rule is stored here.\n  optional Rule rule = 2;\n\n  // A file that is not generated by the build system (version controlled\n  // or created by the test harness).\n  optional SourceFile source_file = 3;\n\n  // A generated file that is the output of a rule.\n  optional GeneratedFile generated_file = 4;\n\n  // A package group.\n  optional PackageGroup package_group = 5;\n\n  // An environment group.\n  optional EnvironmentGroup environment_group = 6;\n}\n\n// Container for all of the blaze query results.\nmessage QueryResult {\n  // All of the targets returned by the blaze query.\n  repeated Target target = 1;\n}\n\n////////////////////////////////////////////////////////////////////////////\n// Messages dealing with querying the BUILD language itself. For now, this is\n// quite simplistic: Blaze can only tell the names of the rule classes, their\n// attributes with their type.\n\n// Information about allowed rule classes for a specific attribute of a rule.\nmessage AllowedRuleClassInfo {\n  enum AllowedRuleClasses {\n    ANY = 1;  // Any rule is allowed to be in this attribute\n    SPECIFIED = 2;  // Only the explicitly listed rules are allowed\n  }\n\n  required AllowedRuleClasses policy = 1;\n\n  // Rule class names of rules allowed in this attribute, e.g \"cc_library\",\n  // \"py_binary\". Only present if the allowed_rule_classes field is set to\n  // SPECIFIED.\n  repeated string allowed_rule_class = 2;\n}\n\n// This message represents a single attribute of a single rule.\n// See docs.bazel.build/versions/master/skylark/lib/attr.html.\nmessage AttributeDefinition {\n  required string name = 1; // e.g. \"name\", \"srcs\"\n  required Attribute.Discriminator type = 2;\n  optional bool mandatory = 3;\n  optional AllowedRuleClassInfo allowed_rule_classes = 4; // type=label*\n  optional string documentation = 5;\n  optional bool allow_empty = 6;       // type=*_list|*_dict\n  optional bool allow_single_file = 7; // type=label\n  optional AttributeValue default = 9; // simple (not computed/late-bound) values only\n  optional bool executable = 10;       // type=label\n  optional bool configurable = 11;\n  optional bool nodep = 12;            // label-valued edge does not establish a dependency\n  optional bool cfg_is_host = 13;      // edge entails a transition to \"host\" configuration\n}\n\n// An AttributeValue represents the value of an attribute.\n// A single field, determined by the attribute type, is populated.\n//\n// It is used only for AttributeDefinition.default. Attribute and\n// SelectorEntry do their own thing for unfortunate historical reasons.\nmessage AttributeValue {\n  optional int32 int = 1;           // type=int|tristate\n  optional string string = 2;       // type=string|label|output\n  optional bool bool = 3;           // type=bool\n  repeated AttributeValue list = 4; // type=*_list|distrib\n  repeated DictEntry dict = 5;      // type=*_dict\n\n  message DictEntry {\n    required string key = 1;\n    required AttributeValue value = 2;\n  }\n}\n\nmessage RuleDefinition {\n  required string name = 1;\n  // Only contains documented attributes\n  repeated AttributeDefinition attribute = 2;\n  optional string documentation = 3;\n  // Only for build extensions: label to file that defines the extension\n  optional string label = 4;\n}\n\nmessage BuildLanguage {\n  // Only contains documented rule definitions\n  repeated RuleDefinition rule = 1;\n}\n"
  },
  {
    "path": "gitops/commitmsg/BUILD.bazel",
    "content": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License. You may obtain a copy\n# of the License at http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software distributed under\n# the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n# OF ANY KIND, either express or implied. See the License for the specific language\n# governing permissions and limitations under the License.\n\nload(\"@rules_go//go:def.bzl\", \"go_library\", \"go_test\")\n\nlicenses([\"notice\"])  # Apache 2.0\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"commitmsg.go\"],\n    importpath = \"github.com/adobe/rules_gitops/gitops/commitmsg\",\n    visibility = [\"//visibility:public\"],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    srcs = [\"commitmsg_test.go\"],\n    deps = [\":go_default_library\"],\n)\n"
  },
  {
    "path": "gitops/commitmsg/commitmsg.go",
    "content": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\npackage commitmsg\n\nimport (\n\t\"log\"\n\t\"strings\"\n)\n\nconst begin = \"--- gitops targets begin ---\"\nconst end = \"--- gitops targets end ---\"\n\n// ExtractTargets extracts list of gitops targets used in a commit\nfunc ExtractTargets(msg string) (packages []string) {\n\tbetweenMarkers := false\n\tfor _, s := range strings.Split(msg, \"\\n\") {\n\t\tswitch s {\n\t\tcase begin:\n\t\t\tbetweenMarkers = true\n\t\tcase end:\n\t\t\tbetweenMarkers = false\n\t\tdefault:\n\t\t\tif betweenMarkers {\n\t\t\t\tpackages = append(packages, s)\n\t\t\t}\n\t\t}\n\t}\n\tif betweenMarkers {\n\t\tlog.Print(\"Unable to find end marker in commit message\")\n\t}\n\treturn\n}\n\n// Generate generates a commit message from a list of targets, including git branch and commit info above the targets block\nfunc Generate(targets []string, branchName, gitCommit string) string {\n\tvar sb strings.Builder\n\tsb.WriteByte('\\n')\n\tsb.WriteString(\"Branch: \")\n\tsb.WriteString(branchName)\n\tsb.WriteByte('\\n')\n\tsb.WriteString(\"Commit: \")\n\tsb.WriteString(gitCommit)\n\tsb.WriteByte('\\n')\n\tsb.WriteString(begin)\n\tsb.WriteByte('\\n')\n\n\tfor _, t := range targets {\n\t\tsb.WriteString(t)\n\t\tsb.WriteByte('\\n')\n\t}\n\n\tsb.WriteString(end)\n\tsb.WriteByte('\\n')\n\treturn sb.String()\n}\n"
  },
  {
    "path": "gitops/commitmsg/commitmsg_test.go",
    "content": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\npackage commitmsg_test\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/adobe/rules_gitops/gitops/commitmsg\"\n)\n\nfunc TestRoundtrip(t *testing.T) {\n\ttargets := []string{\"target1\", \"target2\"}\n\tmsg := commitmsg.Generate(targets, \"my-branch\", \"abc123\")\n\tt2 := commitmsg.ExtractTargets(msg)\n\tif !reflect.DeepEqual(targets, t2) {\n\t\tt.Errorf(\"Unexpected targets after parsing: %v\", t2)\n\t}\n}\n\nfunc ExampleGenerate() {\n\ttargets := []string{\"target1\", \"target2\"}\n\tmsg := commitmsg.Generate(targets, \"my-branch\", \"abc123\")\n\tfmt.Println(msg)\n\t// Output:\n\t// Branch: my-branch\n\t// Commit: abc123\n\t// --- gitops targets begin ---\n\t// target1\n\t// target2\n\t// --- gitops targets end ---\n}\n"
  },
  {
    "path": "gitops/defs.bzl",
    "content": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License. You may obtain a copy\n# of the License at http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software distributed under\n# the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n# OF ANY KIND, either express or implied. See the License for the specific language\n# governing permissions and limitations under the License.\n\n\"\"\"\nGitOps rules public interface\n\"\"\"\n\nload(\"//gitops/private:gitops.bzl\", _gitops = \"gitops\")\nload(\"//gitops/private:k8s_deploy.bzl\", _k8s_deploy = \"k8s_deploy\")\n\nk8s_deploy = _k8s_deploy\ngitops = _gitops\n"
  },
  {
    "path": "gitops/digester/BUILD.bazel",
    "content": "# Copyright 2024 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License. You may obtain a copy\n# of the License at http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software distributed under\n# the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n# OF ANY KIND, either express or implied. See the License for the specific language\n# governing permissions and limitations under the License.\n\nload(\"@rules_go//go:def.bzl\", \"go_library\")\n\nlicenses([\"notice\"])  # Apache 2.0\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"digester.go\"],\n    importpath = \"github.com/adobe/rules_gitops/gitops/digester\",\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "gitops/digester/digester.go",
    "content": "/*\nCopyright 2024 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\npackage digester\n\nimport (\n\t\"crypto/sha256\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n)\n\n// CalculateDigest calculates the SHA256 digest of a file specified by the given path\nfunc CalculateDigest(path string) string {\n\tif _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) {\n\t\treturn \"\"\n\t}\n\n\tfi, err := os.Open(path)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer fi.Close()\n\n\th := sha256.New()\n\tif _, err := io.Copy(h, fi); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\treturn hex.EncodeToString(h.Sum(nil))\n}\n\n// GetDigest retrieves the digest of a file from a file with the same name but with a \".digest\" extension\nfunc GetDigest(path string) string {\n\tdigestPath := path + \".digest\"\n\n\tif _, err := os.Stat(digestPath); errors.Is(err, os.ErrNotExist) {\n\t\treturn \"\"\n\t}\n\n\tdigest, err := os.ReadFile(digestPath)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\treturn string(digest)\n}\n\n// VerifyDigest verifies the integrity of a file by comparing its calculated digest with the stored digest\nfunc VerifyDigest(path string) bool {\n\treturn CalculateDigest(path) == GetDigest(path)\n}\n\n// 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.\nfunc SaveDigest(path string) {\n\tdigest := CalculateDigest(path)\n\n\tdigestPath := path + \".digest\"\n\n\terr := os.WriteFile(digestPath, []byte(digest), 0666)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "gitops/exec/BUILD",
    "content": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License. You may obtain a copy\n# of the License at http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software distributed under\n# the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n# OF ANY KIND, either express or implied. See the License for the specific language\n# governing permissions and limitations under the License.\n\nload(\"@rules_go//go:def.bzl\", \"go_library\")\n\nlicenses([\"notice\"])  # Apache 2.0\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"exec.go\"],\n    importpath = \"github.com/adobe/rules_gitops/gitops/exec\",\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "gitops/exec/BUILD.bazel",
    "content": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License. You may obtain a copy\n# of the License at http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software distributed under\n# the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n# OF ANY KIND, either express or implied. See the License for the specific language\n# governing permissions and limitations under the License.\n\nload(\"@rules_go//go:def.bzl\", \"go_library\")\n\nlicenses([\"notice\"])  # Apache 2.0\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"exec.go\"],\n    importpath = \"github.com/adobe/rules_gitops/gitops/exec\",\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "gitops/exec/exec.go",
    "content": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\npackage exec\n\nimport (\n\t\"log\"\n\t\"os/exec\"\n\t\"strings\"\n)\n\n// Ex is a shortcut for executing the command in specified dir\nfunc Ex(dir, name string, arg ...string) (output string, err error) {\n\tlog.Println(\"executing:\", name, strings.Join(arg, \" \"))\n\tcmd := exec.Command(name, arg...)\n\tif dir != \"\" {\n\t\tcmd.Dir = dir\n\t}\n\tb, err := cmd.CombinedOutput()\n\tlog.Printf(\"%s\", string(b))\n\treturn string(b), err\n}\n\n// Mustex executes the command name arg... in directory dir\n// it will exit with fatal error if execution was not successful\nfunc Mustex(dir, name string, arg ...string) {\n\t_, err := Ex(dir, name, arg...)\n\tif err != nil {\n\t\tlog.Fatalf(\"ERROR: %s\", err)\n\t}\n\n}\n"
  },
  {
    "path": "gitops/git/BUILD.bazel",
    "content": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License. You may obtain a copy\n# of the License at http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software distributed under\n# the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n# OF ANY KIND, either express or implied. See the License for the specific language\n# governing permissions and limitations under the License.\n\nload(\"@rules_go//go:def.bzl\", \"go_library\")\n\nlicenses([\"notice\"])  # Apache 2.0\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\n        \"git.go\",\n        \"server.go\",\n    ],\n    importpath = \"github.com/adobe/rules_gitops/gitops/git\",\n    visibility = [\"//visibility:public\"],\n    deps = [\"//gitops/exec:go_default_library\"],\n)\n"
  },
  {
    "path": "gitops/git/bitbucket/BUILD.bazel",
    "content": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License. You may obtain a copy\n# of the License at http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software distributed under\n# the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n# OF ANY KIND, either express or implied. See the License for the specific language\n# governing permissions and limitations under the License.\n\nload(\"@rules_go//go:def.bzl\", \"go_library\", \"go_test\")\n\nlicenses([\"notice\"])  # Apache 2.0\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"bitbucket.go\"],\n    importpath = \"github.com/adobe/rules_gitops/gitops/git/bitbucket\",\n    visibility = [\"//visibility:public\"],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    srcs = [\"bitbucket_test.go\"],\n    data = glob([\"testdata/**\"]),\n    embed = [\":go_default_library\"],\n)\n"
  },
  {
    "path": "gitops/git/bitbucket/bitbucket.go",
    "content": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\npackage bitbucket\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n)\n\nvar (\n\tapiEndpoint       = 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\")\n\tbitbucketUser     = flag.String(\"bitbucket_user\", os.Getenv(\"BITBUCKET_USER\"), \"bitbucket api user\")\n\tbitbucketPassword = flag.String(\"bitbucket_password\", os.Getenv(\"BITBUCKET_PASSWORD\"), \"bitbucket api user password\")\n)\n\ntype project struct {\n\tKey string `json:\"key,omitempty\"`\n}\n\ntype repository struct {\n\tSlug    string  `json:\"slug,omitempty\"`\n\tProject project `json:\"project\"`\n}\n\ntype pullrequestEndpoint struct {\n\tID         string     `json:\"id,omitempty\"`\n\tRepository repository `json:\"repository,omitempty\"`\n}\n\ntype account struct {\n\tUser user `json:\"user\"`\n}\n\ntype user struct {\n\tName string `json:\"name,omitempty\"`\n}\n\ntype pullrequest struct {\n\tTitle       string               `json:\"title,omitempty\"`\n\tDescription string               `json:\"description,omitempty\"`\n\tState       string               `json:\"state,omitempty\"`\n\tOpen        bool                 `json:\"open\"`\n\tClosed      bool                 `json:\"closed\"`\n\tFromRef     *pullrequestEndpoint `json:\"fromRef,omitempty\"`\n\tToRef       *pullrequestEndpoint `json:\"toRef,omitempty\"`\n\tLocked      bool                 `json:\"locked\"`\n\tReviewers   []account            `json:\"reviewers,omitempty\"`\n}\n\n// CreatePR creates a pull request using branch names from and to\nfunc CreatePR(from, to, title, body string) error {\n\trepo := repository{\n\t\tSlug:    \"repo\",\n\t\tProject: project{\"TM\"},\n\t}\n\tprReq := pullrequest{\n\t\tTitle:       title,\n\t\tDescription: body,\n\t\tState:       \"OPEN\",\n\t\tOpen:        true,\n\t\tClosed:      false,\n\t\tFromRef: &pullrequestEndpoint{\n\t\t\tID:         \"refs/heads/\" + from,\n\t\t\tRepository: repo,\n\t\t},\n\t\tToRef: &pullrequestEndpoint{\n\t\t\tID:         \"refs/heads/\" + to,\n\t\t\tRepository: repo,\n\t\t},\n\t\tLocked:    false,\n\t\tReviewers: []account{},\n\t}\n\tjson, err := json.Marshal(&prReq)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Unable to marshal CreatePR request: %w\", err)\n\t}\n\treq, err := http.NewRequest(\"POST\", *apiEndpoint, bytes.NewBuffer(json))\n\tif err != nil {\n\t\treturn err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json; charset=utf-8\")\n\treq.SetBasicAuth(*bitbucketUser, *bitbucketPassword)\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"Unable to send CreatePR request: %w\", err)\n\t}\n\tlog.Printf(\"bitbucket api response: %s\", resp.Status)\n\tdefer resp.Body.Close()\n\tresponseBody, err := ioutil.ReadAll(resp.Body)\n\tlog.Print(\"bitbucket response: \", string(responseBody))\n\t// 201 created\n\t// 409 already exists\n\tif resp.StatusCode == 201 {\n\t\tlog.Print(\"PR was created\")\n\t\treturn nil\n\t}\n\tif resp.StatusCode == 409 {\n\t\tlog.Print(\"reusing existing PR\")\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"Unrecognized bitbucket response %d\", resp.StatusCode)\n}\n"
  },
  {
    "path": "gitops/git/bitbucket/bitbucket_test.go",
    "content": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\npackage bitbucket\n\nimport (\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"testing\"\n)\n\nfunc TestCreatePRRemote(t *testing.T) {\n\tt.Skip(\"Manual\")\n\tuser := \"********\"\n\tpass := \"*************\"\n\tbitbucketUser = &user\n\tbitbucketPassword = &pass\n\terr := CreatePR(\"deploy/test1\", \"feature/AP-0000\", \"test\", \"hello world\")\n\tif err != nil {\n\t\tt.Error(\"Unexpected error from server: \", err)\n\t}\n}\n\nfunc TestCreatePRNew(t *testing.T) {\n\tvar buf []byte\n\tvar srverr error\n\tts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tbuf, srverr = ioutil.ReadAll(r.Body)\n\t\thttp.Error(w, \"Created\", 201)\n\t\tfmt.Fprintln(w, \"PR created\")\n\t}))\n\tdefer ts.Close()\n\toldendpoint := *apiEndpoint\n\tdefer func() { *apiEndpoint = oldendpoint }()\n\t*apiEndpoint = ts.URL\n\terr := CreatePR(\"deploy/test1\", \"feature/AP-0000\", \"test\", \"hello world\")\n\tif err != nil {\n\t\tt.Error(\"Unexpected error from server: \", err)\n\t}\n\tif srverr != nil {\n\t\tt.Error(\"Unexpected error: \", srverr)\n\t}\n\texpectedreq := `{\"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}`\n\tif string(buf) != expectedreq {\n\t\tt.Error(\"Unexpected request body: \", string(buf))\n\t}\n}\n"
  },
  {
    "path": "gitops/git/bitbucket/testdata/create_pr.json",
    "content": "{\n    \"title\": \"Deploy to test 1\",\n    \"description\": \"Deploy to test 1.\",\n    \"state\": \"OPEN\",\n    \"open\": true,\n    \"closed\": false,\n    \"fromRef\": {\n        \"id\": \"refs/heads/deploy/test_1\",\n        \"repository\": {\n            \"slug\": \"repo\",\n            \"name\": null,\n            \"project\": {\n                \"key\": \"TM\"\n            }\n        }\n    },\n    \"toRef\": {\n        \"id\": \"refs/heads/master\",\n        \"repository\": {\n            \"slug\": \"repo\",\n            \"name\": null,\n            \"project\": {\n                \"key\": \"TM\"\n            }\n        }\n    },\n    \"locked\": false,\n    \"reviewers\": [\n        {\n            \"user\": {\n                \"name\": \"aleksey.pesternikov\"\n            }\n        }\n    ]\n}\n"
  },
  {
    "path": "gitops/git/git.go",
    "content": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\npackage git\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"os\"\n\toe \"os/exec\"\n\t\"path/filepath\"\n\t\"strings\"\n\n\t\"github.com/adobe/rules_gitops/gitops/exec\"\n)\n\nvar (\n\tgit = \"git\"\n)\n\n// Clone clones a repository. Pass the full repository name, such as\n// \"https://aleksey.pesternikov@bitbucket.tubemogul.info/scm/tm/repo.git\" as the repo.\n// Cloned directory will be clean of local changes with primaryBranch branch checked out.\n// repo: https://aleksey.pesternikov@bitbucket.tubemogul.info/scm/tm/repo.git\n// dir: /tmp/cloudrepo\n// mirrorDir: optional (if not empty) local mirror of the repository\nfunc Clone(repo, dir, mirrorDir, primaryBranch, gitopsPath string) (*Repo, error) {\n\tif err := os.RemoveAll(dir); err != nil {\n\t\treturn nil, fmt.Errorf(\"Unable to clone repo: %w\", err)\n\t}\n\tremoteName := \"origin\"\n\targs := []string{\"clone\", \"--no-checkout\", \"--single-branch\", \"--branch\", primaryBranch, \"--filter=blob:none\", \"--no-tags\", \"--origin\", remoteName}\n\tif mirrorDir != \"\" {\n\t\targs = append(args, \"--reference\", mirrorDir)\n\t}\n\targs = append(args, repo, dir)\n\texec.Mustex(\"\", \"git\", args...)\n\t// Enable sparse-checkout when restricting to a subdir\n\tif !isRootPath(gitopsPath) {\n\t\texec.Mustex(dir, \"git\", \"config\", \"--local\", \"core.sparsecheckout\", \"true\")\n\t\tgenPath := fmt.Sprintf(\"%s/\\n\", gitopsPath)\n\t\tif err := ioutil.WriteFile(filepath.Join(dir, \".git/info/sparse-checkout\"), []byte(genPath), 0644); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"Unable to create .git/info/sparse-checkout: %w\", err)\n\t\t}\n\t}\n\texec.Mustex(dir, \"git\", \"checkout\", primaryBranch)\n\treturn &Repo{\n\t\tDir:        dir,\n\t\tRemoteName: remoteName,\n\t}, nil\n}\n\n// Repo is a clone of a git repository. Create with Clone, and don't\n// forget to clean it up after.\ntype Repo struct {\n\t// Dir is the location of the git repo.\n\tDir string\n\t// RemoteName is the name of the remote that tracks upstream repository.\n\tRemoteName string\n}\n\n// Clean cleans up the repo\nfunc (r *Repo) Clean() error {\n\treturn os.RemoveAll(r.Dir)\n}\n\n// Fetch branches from the remote repository based on a specified pattern.\n// The branches will be be added to the list tracked remote branches ready to be pushed.\nfunc (r *Repo) Fetch(pattern string) {\n\texec.Mustex(r.Dir, \"git\", \"remote\", \"set-branches\", \"--add\", r.RemoteName, pattern)\n\texec.Mustex(r.Dir, \"git\", \"fetch\", \"--force\", \"--filter=blob:none\", \"--no-tags\", r.RemoteName)\n}\n\n// SwitchToBranch switch the repo to specified branch and checkout primaryBranch files over it.\n// if branch does not exist it will be created\nfunc (r *Repo) SwitchToBranch(branch, primaryBranch string) (new bool) {\n\tif _, err := exec.Ex(r.Dir, \"git\", \"checkout\", branch); err != nil {\n\t\t// error checking out, create new\n\t\texec.Mustex(r.Dir, \"git\", \"branch\", branch, primaryBranch)\n\t\texec.Mustex(r.Dir, \"git\", \"checkout\", branch)\n\t\treturn true\n\t}\n\treturn false\n}\n\n// RecreateBranch discards a branch content and reset it from primaryBranch.\nfunc (r *Repo) RecreateBranch(branch, primaryBranch string) {\n\texec.Mustex(r.Dir, \"git\", \"checkout\", primaryBranch)\n\texec.Mustex(r.Dir, \"git\", \"branch\", \"-f\", branch, primaryBranch)\n\texec.Mustex(r.Dir, \"git\", \"checkout\", branch)\n}\n\n// GetLastCommitMessage fetches the commit message from the most recent change of the branch\nfunc (r *Repo) GetLastCommitMessage() (msg string) {\n\tmsg, err := exec.Ex(r.Dir, \"git\", \"log\", \"-1\", \"--pretty=%B\")\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\treturn msg\n}\n\n// Commit all changes to the current branch. returns true if there were any changes\nfunc (r *Repo) Commit(message, gitopsPath string) bool {\n\tif isRootPath(gitopsPath) {\n\t\texec.Mustex(r.Dir, \"git\", \"add\", \".\")\n\t} else {\n\t\texec.Mustex(r.Dir, \"git\", \"add\", gitopsPath)\n\t}\n\tif r.IsClean() {\n\t\treturn false\n\t}\n\texec.Mustex(r.Dir, \"git\", \"commit\", \"-a\", \"-m\", message)\n\treturn true\n}\n\n// RestoreFile restores the specified file in the repository to its original state\nfunc (r *Repo) RestoreFile(fileName string) {\n\texec.Mustex(r.Dir, \"git\", \"checkout\", \"--\", fileName)\n}\n\n// GetChangedFiles returns a list of files that have been changed in the repository\nfunc (r *Repo) GetChangedFiles() []string {\n\ts, err := exec.Ex(r.Dir, \"git\", \"diff\", \"--name-only\")\n\tif err != nil {\n\t\tlog.Fatalf(\"ERROR: %s\", err)\n\t}\n\tvar files []string\n\tsc := bufio.NewScanner(strings.NewReader(s))\n\tfor sc.Scan() {\n\t\tfiles = append(files, sc.Text())\n\t}\n\tif err := sc.Err(); err != nil {\n\t\tlog.Fatalf(\"ERROR: %s\", err)\n\t}\n\treturn files\n}\n\n// IsClean returns true if there is no local changes (nothing to commit)\nfunc (r *Repo) IsClean() bool {\n\tcmd := oe.Command(\"git\", \"status\", \"--porcelain\")\n\tcmd.Dir = r.Dir\n\tb, err := cmd.CombinedOutput()\n\tif err != nil {\n\t\tlog.Fatalf(\"ERROR: %s\", err)\n\t}\n\treturn len(b) == 0\n}\n\n// Push pushes all local changes to the remote repository\n// all changes should be already commited\nfunc (r *Repo) Push(branches []string) {\n\targs := append([]string{\"push\", r.RemoteName, \"-f\", \"--set-upstream\"}, branches...)\n\texec.Mustex(r.Dir, \"git\", args...)\n}\n\n// isRootPath is an internal helper to detect \"full repo\" case.\nfunc isRootPath(gitopsPath string) bool {\n\treturn gitopsPath == \"\" || gitopsPath == \".\"\n}"
  },
  {
    "path": "gitops/git/github/BUILD.bazel",
    "content": "load(\"@rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"github.go\"],\n    importpath = \"github.com/adobe/rules_gitops/gitops/git/github\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@com_github_google_go_github_v32//github:go_default_library\",\n        \"@org_golang_x_oauth2//:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "gitops/git/github/github.go",
    "content": "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-github/v32/github\"\n\t\"golang.org/x/oauth2\"\n)\n\nvar (\n\trepoOwner            = flag.String(\"github_repo_owner\", \"\", \"the owner user/organization to use for github api requests\")\n\trepo                 = flag.String(\"github_repo\", \"\", \"the repo to use for github api requests\")\n\tpat                  = flag.String(\"github_access_token\", os.Getenv(\"GITHUB_TOKEN\"), \"the access token to authenticate requests\")\n\tgithubEnterpriseHost = flag.String(\"github_enterprise_host\", \"\", \"The host name of the private enterprise github, e.g. git.corp.adobe.com\")\n)\n\nfunc CreatePR(from, to, title, body string) error {\n\tif *repoOwner == \"\" {\n\t\treturn errors.New(\"github_repo_owner must be set\")\n\t}\n\tif *repo == \"\" {\n\t\treturn errors.New(\"github_repo must be set\")\n\t}\n\tif *pat == \"\" {\n\t\treturn errors.New(\"github_access_token must be set\")\n\t}\n\n\tctx := context.Background()\n\tts := oauth2.StaticTokenSource(\n\t\t&oauth2.Token{AccessToken: *pat},\n\t)\n\ttc := oauth2.NewClient(ctx, ts)\n\n\tvar gh *github.Client\n\tif *githubEnterpriseHost != \"\" {\n\t\tbaseUrl := \"https://\" + *githubEnterpriseHost + \"/api/v3/\"\n\t\tuploadUrl := \"https://\" + *githubEnterpriseHost + \"/api/uploads/\"\n\t\tvar err error\n\t\tgh, err = github.NewEnterpriseClient(baseUrl, uploadUrl, tc)\n\t\tif err != nil {\n\t\t\tlog.Println(\"Error in creating github client\", err)\n\t\t\treturn nil\n\t\t}\n\t} else {\n\t\tgh = github.NewClient(tc)\n\t}\n\n\tpr := &github.NewPullRequest{\n\t\tTitle:               &title,\n\t\tHead:                &from,\n\t\tBase:                &to,\n\t\tBody:                &body,\n\t\tIssue:               nil,\n\t\tMaintainerCanModify: new(bool),\n\t\tDraft:               new(bool),\n\t}\n\tcreatedPr, resp, err := gh.PullRequests.Create(ctx, *repoOwner, *repo, pr)\n\tif err == nil {\n\t\tlog.Println(\"Created PR: \", *createdPr.URL)\n\t\treturn err\n\t}\n\n\tif resp.StatusCode == http.StatusUnprocessableEntity {\n\t\t// Handle the case: \"Create PR\" request fails because it already exists\n\t\tlog.Println(\"Reusing existing PR\")\n\t\treturn nil\n\t}\n\n\t// All other github responses\n\tdefer resp.Body.Close()\n\tresponseBody, readingErr := ioutil.ReadAll(resp.Body)\n\tif readingErr != nil {\n\t\tlog.Println(\"cannot read response body\")\n\t} else {\n\t\tlog.Println(\"github response: \", string(responseBody))\n\t}\n\n\treturn err\n}\n"
  },
  {
    "path": "gitops/git/gitlab/BUILD.bazel",
    "content": "load(\"@rules_go//go:def.bzl\", \"go_library\", \"go_test\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"gitlab.go\"],\n    importpath = \"github.com/adobe/rules_gitops/gitops/git/gitlab\",\n    visibility = [\"//visibility:public\"],\n    deps = [\"@com_github_xanzy_go_gitlab//:go_default_library\"],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    srcs = [\"gitlab_test.go\"],\n    embed = [\":go_default_library\"],\n)\n"
  },
  {
    "path": "gitops/git/gitlab/gitlab.go",
    "content": "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 (\n\tgitlabHost  = flag.String(\"gitlab_host\", \"https://gitlab.com\", \"The host name of the gitlab instance\")\n\trepo        = flag.String(\"gitlab_repo\", \"\", \"the repo to use for gitlab api requests\")\n\taccessToken = flag.String(\"gitlab_access_token\", os.Getenv(\"GITLAB_TOKEN\"), \"the access token to authenticate requests\")\n)\n\nfunc CreatePR(from, to, title, body string) error {\n\tif *accessToken == \"\" {\n\t\treturn errors.New(\"gitlab_access_token must be set\")\n\t}\n\n\topts := gitlab.CreateMergeRequestOptions{\n\t\tTitle:              &title,\n\t\tDescription:        nil,\n\t\tSourceBranch:       &from,\n\t\tTargetBranch:       &to,\n\t\tLabels:             nil,\n\t\tAssigneeID:         nil,\n\t\tAssigneeIDs:        nil,\n\t\tReviewerIDs:        nil,\n\t\tTargetProjectID:    nil,\n\t\tMilestoneID:        nil,\n\t\tRemoveSourceBranch: nil,\n\t\tSquash:             nil,\n\t\tAllowCollaboration: nil,\n\t}\n\n\tgl, err := gitlab.NewClient(*accessToken, gitlab.WithBaseURL(*gitlabHost))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcreatedPr, resp, err := gl.MergeRequests.CreateMergeRequest(*repo, &opts)\n\tif err == nil {\n\t\tlog.Println(\"Created MR: \", createdPr.WebURL)\n\t\treturn nil\n\t}\n\n\tif resp.StatusCode == http.StatusConflict {\n\t\t// Handle the case: \"Create MR\" request fails because it already exists for this source branch\n\t\tlog.Println(\"Reusing existing MR\")\n\t\treturn nil\n\t}\n\n\t// All other gitlab responses\n\tdefer resp.Body.Close()\n\tresponseBody, readingErr := ioutil.ReadAll(resp.Body)\n\tif readingErr != nil {\n\t\tlog.Println(\"cannot read response body\")\n\t} else {\n\t\tlog.Println(\"gitlab response: \", string(responseBody))\n\t}\n\n\treturn err\n}\n"
  },
  {
    "path": "gitops/git/gitlab/gitlab_test.go",
    "content": "package gitlab\n\nimport \"testing\"\n\nfunc TestCreatePRRemote(t *testing.T) {\n\tt.Skip(\"Manual\")\n\tvar (\n\t\ttestGitlabToken = \"********\"\n\t)\n\taccessToken = &testGitlabToken\n\ttype args struct {\n\t\tfrom  string\n\t\tto    string\n\t\ttitle string\n\t\tbody  string\n\t}\n\ttests := []struct {\n\t\trepo    string\n\t\targs    args\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\trepo: \"cotocisternas/rules_gitops_gitlab_test\",\n\t\t\targs: args{\n\t\t\t\tfrom:  \"feature/gitlab-test\",\n\t\t\t\tto:    \"master\",\n\t\t\t\ttitle: \"test_gitlab\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\trepo: \"cotocisternas/rules_gitops_gitlab_test\",\n\t\t\targs: args{\n\t\t\t\tfrom:  \"feature/gitlab-test\",\n\t\t\t\tto:    \"master\",\n\t\t\t\ttitle: \"test_gitlab\",\n\t\t\t\tbody:  \"hello world\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t\t{\n\t\t\trepo: \"petabytecl/subgroup_rules_gitops_gitlab_test/rules_gitops_gitlab_test\",\n\t\t\targs: args{\n\t\t\t\tfrom:  \"feature/gitlab-test\",\n\t\t\t\tto:    \"master\",\n\t\t\t\ttitle: \"test_gitlab\",\n\t\t\t\tbody:  \"hello world\",\n\t\t\t},\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.repo, func(t *testing.T) {\n\t\t\trepo = &tt.repo\n\t\t\tif err := CreatePR(tt.args.from, tt.args.to, tt.args.title, tt.args.body); (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"CreatePR() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "gitops/git/server.go",
    "content": "package git\n\ntype Server interface {\n\tCreatePR(from, to, title, body string) error\n}\n\ntype ServerFunc func(from, to, title, body string) error\n\nfunc (f ServerFunc) CreatePR(from, to, title, body string) error {\n\tif body == \"\" {\n\t\tbody = title\n\t}\n\n\treturn f(from, to, title, body)\n}\n"
  },
  {
    "path": "gitops/prer/BUILD.bazel",
    "content": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License. You may obtain a copy\n# of the License at http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software distributed under\n# the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n# OF ANY KIND, either express or implied. See the License for the specific language\n# governing permissions and limitations under the License.\n\nload(\"@rules_go//go:def.bzl\", \"go_binary\", \"go_library\")\n\nlicenses([\"notice\"])  # Apache 2.0\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"create_gitops_prs.go\"],\n    importpath = \"github.com/adobe/rules_gitops/gitops/prer\",\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"//gitops/analysis:go_default_library\",\n        \"//gitops/bazel:go_default_library\",\n        \"//gitops/commitmsg:go_default_library\",\n        \"//gitops/digester:go_default_library\",\n        \"//gitops/exec:go_default_library\",\n        \"//gitops/git:go_default_library\",\n        \"//gitops/git/bitbucket:go_default_library\",\n        \"//gitops/git/github:go_default_library\",\n        \"//gitops/git/gitlab:go_default_library\",\n        \"//templating/fasttemplate:go_default_library\",\n        \"@org_golang_google_protobuf//proto:go_default_library\",\n    ],\n)\n\ngo_binary(\n    name = \"create_gitops_prs\",\n    embed = [\":go_default_library\"],\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "gitops/prer/create_gitops_prs.go",
    "content": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\npackage main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\toe \"os/exec\"\n\t\"strings\"\n\n\t\"github.com/adobe/rules_gitops/gitops/analysis\"\n\t\"github.com/adobe/rules_gitops/gitops/bazel\"\n\t\"github.com/adobe/rules_gitops/gitops/commitmsg\"\n\t\"github.com/adobe/rules_gitops/gitops/digester\"\n\t\"github.com/adobe/rules_gitops/gitops/exec\"\n\t\"github.com/adobe/rules_gitops/gitops/git\"\n\t\"github.com/adobe/rules_gitops/gitops/git/bitbucket\"\n\t\"github.com/adobe/rules_gitops/gitops/git/github\"\n\t\"github.com/adobe/rules_gitops/gitops/git/gitlab\"\n\t\"github.com/adobe/rules_gitops/templating/fasttemplate\"\n\n\t\"google.golang.org/protobuf/proto\"\n)\n\nfunc init() {\n\tlog.SetFlags(log.LstdFlags | log.Lshortfile)\n}\n\nvar (\n\treleaseBranch          = flag.String(\"release_branch\", \"main\", \"filter gitops targets by release branch\")\n\tbazelCmd               = flag.String(\"bazel_cmd\", \"tools/bazel\", \"bazel binary to use\")\n\tworkspace              = flag.String(\"workspace\", \"\", \"path to workspace root\")\n\trepo                   = flag.String(\"git_repo\", \"\", \"git repo location\")\n\tgitMirror              = flag.String(\"git_mirror\", \"\", \"git mirror location, like /mnt/mirror/bitbucket.tubemogul.info/tm/repo.git for jenkins\")\n\tgitopsPath             = flag.String(\"gitops_path\", \"cloud\", \"location to store files in repo.\")\n\tgitopsTmpDir           = flag.String(\"gitops_tmpdir\", os.TempDir(), \"location to check out git tree with /cloud.\")\n\ttarget                 = flag.String(\"target\", \"//... except //experimental/...\", \"target to scan. Useful for debugging only\")\n\tprInto                 = flag.String(\"gitops_pr_into\", \"main\", \"use this branch as the source branch and target for deployment PR\")\n\tprBody                 = flag.String(\"gitops_pr_body\", \"\", \"a body message for deployment PR\")\n\tprTitle                = flag.String(\"gitops_pr_title\", \"\", \"a title for deployment PR\")\n\tbranchName             = flag.String(\"branch_name\", \"unknown\", \"Branch name to use in commit message\")\n\tgitCommit              = flag.String(\"git_commit\", \"unknown\", \"Git commit to use in commit message\")\n\tdeploymentBranchPrefix = flag.String(\"deployment_branch_prefix\", \"deploy/\", \"the prefix to add to all deployment branch names\")\n\tdeploymentBranchSuffix = flag.String(\"deployment_branch_suffix\", \"\", \"suffix to add to all deployment branch names\")\n\tgitHost                = flag.String(\"git_server\", \"bitbucket\", \"the git server api to use. 'bitbucket', 'github' or 'gitlab'\")\n\tstamp                  = flag.Bool(\"stamp\", false, \"Stamp results of gitops targets with volatile information\")\n\tdryRun                 = flag.Bool(\"dry_run\", false, \"Do not create PRs, just print what would be done\")\n)\n\nfunc bazelQuery(query string) *analysis.CqueryResult {\n\tlog.Println(\"Executing bazel cquery \", query)\n\tcmd := oe.Command(*bazelCmd, \"cquery\", query, \"--output=proto\")\n\tstderr, err := cmd.StderrPipe()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tgo func() {\n\t\tio.Copy(os.Stderr, stderr)\n\t}()\n\tbuildproto, err := cmd.Output()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tqr := &analysis.CqueryResult{}\n\tif err := proto.Unmarshal(buildproto, qr); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\treturn qr\n}\n\nfunc getGitStatusDict(workdir *git.Repo, gitCommit, branchName string) map[string]interface{} {\n\tutcDate, err := exec.Ex(\"\", \"date\", \"-u\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tutcDate = strings.TrimSpace(utcDate)\n\n\tctx := map[string]interface{}{\n\t\t\"GIT_REVISION\": gitCommit,\n\t\t\"UTC_DATE\":     utcDate,\n\t\t\"GIT_BRANCH\":   branchName,\n\t}\n\n\treturn ctx\n}\n\nfunc stampFile(fullPath string, ctx map[string]interface{}) {\n\ttemplate, err := os.ReadFile(fullPath)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\toutf, err := os.OpenFile(fullPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tdefer outf.Close()\n\n\t_, err = fasttemplate.Execute(string(template), \"{{\", \"}}\", outf, ctx)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n\nfunc main() {\n\tflag.Parse()\n\tif *workspace != \"\" {\n\t\tif err := os.Chdir(*workspace); err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t}\n\n\tvar gitServer git.Server\n\tswitch *gitHost {\n\tcase \"github\":\n\t\tgitServer = git.ServerFunc(github.CreatePR)\n\tcase \"gitlab\":\n\t\tgitServer = git.ServerFunc(gitlab.CreatePR)\n\tcase \"bitbucket\":\n\t\tgitServer = git.ServerFunc(bitbucket.CreatePR)\n\tdefault:\n\t\tlog.Fatalf(\"unknown vcs host: %s\", *gitHost)\n\t}\n\n\tq := fmt.Sprintf(\"attr(deployment_branch, \\\".+\\\", attr(release_branch_prefix, \\\"%s\\\", kind(gitops, %s)))\", *releaseBranch, *target)\n\tqr := bazelQuery(q)\n\treleaseTrains := make(map[string][]string)\n\tfor _, t := range qr.Results {\n\t\tvar releaseTrain string\n\t\tfor _, a := range t.Target.GetRule().GetAttribute() {\n\t\t\tif a.GetName() == \"deployment_branch\" {\n\t\t\t\treleaseTrain = a.GetStringValue()\n\t\t\t}\n\t\t}\n\t\treleaseTrains[releaseTrain] = append(releaseTrains[releaseTrain], t.Target.Rule.GetName())\n\t}\n\tif (len(releaseTrains)) == 0 {\n\t\tlog.Println(\"No matching targets found\")\n\t\treturn\n\t}\n\n\tfor train, targets := range releaseTrains {\n\t\tfmt.Println(train)\n\t\tfor _, t := range targets {\n\t\t\tfmt.Println(\" \", t)\n\t\t}\n\t}\n\n\tgitopsdir, err := os.MkdirTemp(*gitopsTmpDir, \"gitops\")\n\tif err != nil {\n\t\tlog.Fatalf(\"Unable to create tempdir in %s: %v\", *gitopsTmpDir, err)\n\t}\n\tdefer os.RemoveAll(gitopsdir)\n\tworkdir, err := git.Clone(*repo, gitopsdir, *gitMirror, *prInto, *gitopsPath)\n\tif err != nil {\n\t\tlog.Fatalf(\"Unable to clone repo: %v\", err)\n\t}\n\tworkdir.Fetch(*deploymentBranchPrefix + \"*\")\n\n\tvar updatedGitopsBranches []string\n\n\tfor train, targets := range releaseTrains {\n\t\tlog.Println(\"train\", train)\n\t\tbranch := *deploymentBranchPrefix + train + *deploymentBranchSuffix\n\t\tnewBranch := workdir.SwitchToBranch(branch, *prInto)\n\t\tif !newBranch {\n\t\t\t// Find if we need to recreate the branch because target was deleted\n\t\t\tmsg := workdir.GetLastCommitMessage()\n\t\t\ttargetset := make(map[string]bool)\n\t\t\tfor _, t := range targets {\n\t\t\t\ttargetset[t] = true\n\t\t\t}\n\t\t\toldtargets := commitmsg.ExtractTargets(msg)\n\t\t\tfor _, t := range oldtargets {\n\t\t\t\tif !targetset[t] {\n\t\t\t\t\t// target t is not present in a new list\n\t\t\t\t\tworkdir.RecreateBranch(branch, *prInto)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor _, target := range targets {\n\t\t\tlog.Println(\"train\", train, \"target\", target)\n\t\t\tbin := bazel.TargetToExecutable(target)\n\t\t\texec.Mustex(\"\", bin, \"--deployment_root\", gitopsdir)\n\t\t}\n\t\tif *stamp {\n\t\t\tchangedFiles := workdir.GetChangedFiles()\n\t\t\tif len(changedFiles) > 0 {\n\t\t\t\tctx := getGitStatusDict(workdir, *gitCommit, *branchName)\n\t\t\t\tfor _, filePath := range changedFiles {\n\t\t\t\t\tfullPath := gitopsdir + \"/\" + filePath\n\t\t\t\t\tif digester.VerifyDigest(fullPath) {\n\t\t\t\t\t\tworkdir.RestoreFile(fullPath)\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdigester.SaveDigest(fullPath)\n\t\t\t\t\t\tstampFile(fullPath, ctx)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif workdir.Commit(fmt.Sprintf(\"GitOps for release branch %s from %s commit %s\\n%s\", *releaseBranch, *branchName, *gitCommit, commitmsg.Generate(targets, *branchName, *gitCommit)), *gitopsPath) {\n\t\t\tlog.Println(\"branch\", branch, \"has changes, push is required\")\n\t\t\tupdatedGitopsBranches = append(updatedGitopsBranches, branch)\n\t\t}\n\t}\n\tif len(updatedGitopsBranches) == 0 {\n\t\tlog.Println(\"No gitops changes to push\")\n\t\treturn\n\t}\n\n\tif *dryRun {\n\t\tlog.Println(\"dry-run: updated gitops branches: \", updatedGitopsBranches)\n\t\tlog.Println(\"dry-run: skipping push\")\n\t} else {\n\t\tworkdir.Push(updatedGitopsBranches)\n\t}\n\n\tfor _, branch := range updatedGitopsBranches {\n\t\tif *dryRun {\n\t\t\tlog.Println(\"dry-run: skipping PR creation: branch \", branch, \"into \", *prInto)\n\t\t\tcontinue\n\t\t}\n\n\t\ttitle := *prTitle\n\t\tif title == \"\" {\n\t\t\ttitle = fmt.Sprintf(\"GitOps deployment %s\", branch)\n\t\t}\n\n\t\tbody := *prBody\n\t\tif body == \"\" {\n\t\t\tbody = branch\n\t\t}\n\n\t\tif err := gitServer.CreatePR(branch, *prInto, title, body); err != nil {\n\t\t\tlog.Fatal(\"unable to create PR: \", err)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "gitops/private/BUILD.bazel",
    "content": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\n\npackage(\n    default_visibility = [\"//gitops:__subpackages__\"],\n)\n\nexports_files([\n    \"nameprefix_deployment_labels_config.yaml\",\n    \"namesuffix_deployment_labels_config.yaml\",\n    \"k8s_gitops.sh.tpl\",\n])\n\nbzl_library(\n    name = \"k8s_deploy\",\n    srcs = [\"k8s_deploy.bzl\"],\n    deps = [\n        \":gitops\",\n        \"//kubectl:defs\",\n        \"//kustomize:defs\",\n    ],\n)\n\nbzl_library(\n    name = \"gitops\",\n    srcs = [\"gitops.bzl\"],\n    deps = [\n        \"//adapters:providers\",\n        \"//kustomize:defs\",\n    ],\n)\n"
  },
  {
    "path": "gitops/private/gitops.bzl",
    "content": "\"\"\"GitOps rule for generating deployment manifests.\"\"\"\n\nload(\"//adapters:providers.bzl\", \"K8sPushInfo\")\nload(\"//kustomize:defs.bzl\", \"KustomizeInfo\")\n\n# Convert short_path to runfiles manifest path for use with rlocation\ndef _to_manifest_path(ctx, file):\n    if file.short_path.startswith(\"../\"):\n        return \"external/\" + file.short_path[3:]\n    else:\n        return ctx.workspace_name + \"/\" + file.short_path\n\ndef _image_push_statements(\n        ctx,\n        kustomize_objs,\n        files = []):\n    statements = \"\"\n    trans_img_pushes = depset(transitive = [obj[KustomizeInfo].image_pushes for obj in kustomize_objs]).to_list()\n\n    statements += \"\\n\".join([\n        \"echo  pushing {}/{}\".format(exe[K8sPushInfo].registry, exe[K8sPushInfo].repository)\n        for exe in trans_img_pushes\n        if hasattr(exe[K8sPushInfo], \"pusher\")\n    ]) + \"\\n\"\n    statements += \"\\n\".join([\n        \"  async \\\"$(rlocation %s)\\\"\" % _to_manifest_path(ctx, exe[K8sPushInfo].pusher.files_to_run.executable)\n        for exe in trans_img_pushes\n        if hasattr(exe[K8sPushInfo], \"pusher\")\n    ]) + \"\\n  waitpids\\n\"\n\n    # files += [obj.files_to_run.executable for obj in trans_img_pushes]\n    dep_runfiles = [obj[K8sPushInfo].pusher.default_runfiles for obj in trans_img_pushes if hasattr(obj[K8sPushInfo], \"pusher\")]\n    return statements, files, dep_runfiles\n\ndef _remove_prefix(s, prefix):\n    return s[len(prefix):] if s.startswith(prefix) else s\n\ndef _remove_prefixes(s, prefixes):\n    for prefix in prefixes:\n        s = _remove_prefix(s, prefix)\n    return s\n\ndef _gitops_impl(ctx):\n    cluster = ctx.attr.cluster\n    strip_prefixes = ctx.attr.strip_prefixes\n    files = []\n\n    push_statements, files, pushes_runfiles = _image_push_statements(ctx, ctx.attr.srcs, files)\n    statements = \"\"\n    namespace = ctx.attr.namespace\n    for inattr in ctx.attr.srcs:\n        if \"{\" in namespace:\n            fail(\"unable to gitops namespace with placeholders %s\" % inattr.label)  #mynamespace should not be gitopsed\n        for infile in inattr.files.to_list():\n            statements += (\"echo $TARGET_DIR/{gitops_path}/{namespace}/{cluster}/{file}\\n\" +\n                           \"mkdir -p $TARGET_DIR/{gitops_path}/{namespace}/{cluster}\\n\" +\n                           \"echo '# GENERATED BY {rulename} -> {gitopsrulename}' > $TARGET_DIR/{gitops_path}/{namespace}/{cluster}/{file}\\n\" +\n                           \"{template_engine} --template={infile} --variable=NAMESPACE={namespace} --stamp_info_file={info_file} >> $TARGET_DIR/{gitops_path}/{namespace}/{cluster}/{file}\\n\").format(\n                infile = infile.path,\n                rulename = inattr.label,\n                gitopsrulename = ctx.label,\n                namespace = namespace,\n                gitops_path = ctx.attr.gitops_path,\n                cluster = cluster,\n                file = _remove_prefixes(infile.path.split(\"/\")[-1], strip_prefixes),\n                template_engine = ctx.executable._template_engine.path,\n                info_file = ctx.file._info_file.path,\n            )\n\n    ctx.actions.expand_template(\n        template = ctx.file._template,\n        substitutions = {\n            \"%{deployment_branch}\": ctx.attr.deployment_branch,\n            \"%{push_statements}\": push_statements,\n            \"%{statements}\": statements,\n        },\n        output = ctx.outputs.executable,\n    )\n    runfiles = files + ctx.files.srcs + [ctx.executable._template_engine, ctx.file._info_file]\n    transitive = depset(transitive = [obj.default_runfiles.files for obj in ctx.attr.srcs])\n\n    rf = ctx.runfiles(files = runfiles, transitive_files = transitive).merge(\n        ctx.attr._bash_runfiles[DefaultInfo].default_runfiles,\n    )\n    for dep_rf in pushes_runfiles:\n        rf = rf.merge(dep_rf)\n    return [\n        DefaultInfo(runfiles = rf),\n        KustomizeInfo(\n            image_pushes = depset(transitive = [obj[KustomizeInfo].image_pushes for obj in ctx.attr.srcs]),\n        ),\n    ]\n\ngitops = rule(\n    attrs = {\n        \"srcs\": attr.label_list(providers = (KustomizeInfo,)),\n        \"cluster\": attr.string(mandatory = True),\n        \"namespace\": attr.string(mandatory = True),\n        \"deployment_branch\": attr.string(),\n        \"gitops_path\": attr.string(),\n        \"release_branch_prefix\": attr.string(),\n        \"strip_prefixes\": attr.string_list(),\n        \"_info_file\": attr.label(\n            default = Label(\"//stamper:more_stable_status.txt\"),\n            allow_single_file = True,\n        ),\n        \"_template_engine\": attr.label(\n            default = Label(\"//templating:fast_template_engine\"),\n            executable = True,\n            cfg = \"exec\",\n        ),\n        \"_template\": attr.label(\n            default = Label(\"//gitops/private:k8s_gitops.sh.tpl\"),\n            allow_single_file = True,\n        ),\n        \"_bash_runfiles\": attr.label(\n            default = Label(\"@bazel_tools//tools/bash/runfiles\"),\n        ),\n    },\n    executable = True,\n    implementation = _gitops_impl,\n)\n"
  },
  {
    "path": "gitops/private/k8s_deploy.bzl",
    "content": "\"\"\"Macro for creating Kubernetes deployment targets with GitOps support.\"\"\"\n\nload(\"//gitops/private:gitops.bzl\", _gitops = \"gitops\")\nload(\"//kubectl:defs.bzl\", \"kubectl_binary\")\nload(\"//kustomize:defs.bzl\", \"kustomization\", \"show\")\n\ndef k8s_deploy(\n        name,  # name of the rule is important for gitops, since it will become a part of the target manifest file name in /cloud\n        cluster = \"dev\",\n        user = \"{BUILD_USER}\",\n        namespace = None,\n        configmaps_srcs = None,\n        secrets_srcs = None,\n        configmaps_renaming = None,  # configmaps renaming policy. Could be None or 'hash'.\n        manifests = None,\n        name_prefix = None,\n        name_suffix = None,\n        prefix_suffix_app_labels = False,  # apply kustomize configuration to modify \"app\" labels in Deployments when name prefix or suffix applied\n        patches = None,\n        image_name_patches = {},\n        image_tag_patches = {},\n        substitutions = {},  # dict of template parameter substitutions. CLUSTER and NAMESPACE parameters are added automatically.\n        configurations = [],  # additional kustomize configuration files. rules_gitops provides\n        common_labels = {},  # list of common labels to apply to all objects see commonLabels kustomize docs\n        common_annotations = {},  # list of common annotations to apply to all objects see commonAnnotations kustomize docs\n        deps = [],\n        deps_aliases = {},\n        images = [],\n        objects = [],\n        gitops = True,  # make sure to use gitops = False to work with individual namespace. This option will be turned False if namespace is '{BUILD_USER}'\n        gitops_path = \"cloud\",\n        deployment_branch = None,\n        release_branch_prefix = \"main\",\n        start_tag = \"{{\",\n        end_tag = \"}}\",\n        tags = [],\n        visibility = None,\n        verify_images = True):\n    \"\"\"Generates Kubernetes deployment targets with optional GitOps support.\n\n    This macro creates kustomization targets and kubectl binaries for deploying\n    Kubernetes manifests. When gitops is enabled, it also creates a gitops target\n    for managing deployments through a GitOps workflow.\n\n    Args:\n        name: Name of the rule. Important for gitops since it becomes part of\n            the target manifest file name in the gitops_path directory.\n        cluster: Target Kubernetes cluster name. Defaults to \"dev\".\n        user: Kubernetes user for authentication. Defaults to \"{BUILD_USER}\"\n            which is substituted at runtime.\n        namespace: Target Kubernetes namespace. Required when gitops=True.\n            Defaults to \"{BUILD_USER}\" when gitops=False.\n        configmaps_srcs: List of source files for generating ConfigMaps.\n        secrets_srcs: List of source files for generating Secrets.\n        configmaps_renaming: ConfigMap renaming policy. Can be None (no renaming)\n            or \"hash\" (append content hash to names).\n        manifests: List of Kubernetes manifest files. Defaults to all .yaml and\n            .yaml.tpl files in the package via glob.\n        name_prefix: Prefix to add to all resource names via kustomize.\n        name_suffix: Suffix to add to all resource names via kustomize.\n        prefix_suffix_app_labels: If True, applies kustomize configuration to\n            modify \"app\" labels in Deployments when name_prefix or name_suffix\n            is applied.\n        patches: List of kustomize patches to apply to the manifests.\n        image_name_patches: Dict mapping original image names to new image names.\n        image_tag_patches: Dict mapping image names to new tags.\n        substitutions: Dict of template parameter substitutions. CLUSTER and\n            NAMESPACE parameters are added automatically and should not be\n            included.\n        configurations: List of additional kustomize configuration files.\n        common_labels: Dict of labels to apply to all Kubernetes objects.\n            See kustomize commonLabels documentation.\n        common_annotations: Dict of annotations to apply to all Kubernetes\n            objects. See kustomize commonAnnotations documentation.\n        deps: List of dependency targets.\n        deps_aliases: Dict mapping aliases to dependency targets.\n        images: List of container image targets to include in the deployment.\n        objects: List of additional Kubernetes objects to include.\n        gitops: If True, creates a gitops target for GitOps workflow. Set to\n            False to work with individual namespaces. Automatically set to\n            False if namespace is \"{BUILD_USER}\".\n        gitops_path: Directory path for gitops manifests. Defaults to \"cloud\".\n        deployment_branch: Git branch for deployments. If None, uses default.\n        release_branch_prefix: Prefix for release branches. Defaults to \"main\".\n        start_tag: Opening delimiter for template substitutions. Defaults to \"{{\".\n        end_tag: Closing delimiter for template substitutions. Defaults to \"}}\".\n        tags: List of tags to apply to all generated targets.\n        visibility: Visibility specification for generated targets.\n        verify_images: Whether or not to fail if a Bazel image could not be resolved.\n    \"\"\"\n\n    if not manifests:\n        manifests = native.glob([\"*.yaml\", \"*.yaml.tpl\"])\n    if prefix_suffix_app_labels:\n        configurations = configurations + [\n            str(Label(\"//gitops:nameprefix_deployment_labels_config.yaml\")),\n            str(Label(\"//gitops:namesuffix_deployment_labels_config.yaml\")),\n        ]\n    for reservedname in [\"CLUSTER\", \"NAMESPACE\"]:\n        if substitutions.get(reservedname):\n            fail(\"do not put %s in substitutions parameter of k8s_deploy. It will be added autimatically\" % reservedname)\n    substitutions = dict(substitutions)\n    substitutions[\"CLUSTER\"] = cluster\n\n    # NAMESPACE substitution is deferred until test_setup/kubectl/gitops\n    if namespace == \"{BUILD_USER}\":\n        gitops = False\n\n    if not gitops:\n        # Mynamespace option\n        if not namespace:\n            namespace = \"{BUILD_USER}\"\n        kustomization(\n            name = name,\n            namespace = namespace,\n            configmaps_srcs = configmaps_srcs,\n            secrets_srcs = secrets_srcs,\n            # disable_name_suffix_hash is renamed to configmaps_renaming in recent Kustomize\n            disable_name_suffix_hash = (configmaps_renaming != \"hash\"),\n            images = images,\n            manifests = manifests,\n            substitutions = substitutions,\n            deps = deps,\n            deps_aliases = deps_aliases,\n            start_tag = start_tag,\n            end_tag = end_tag,\n            name_prefix = name_prefix,\n            name_suffix = name_suffix,\n            configurations = configurations,\n            common_labels = common_labels,\n            common_annotations = common_annotations,\n            patches = patches,\n            objects = objects,\n            image_name_patches = image_name_patches,\n            image_tag_patches = image_tag_patches,\n            tags = tags,\n            visibility = visibility,\n            verify_images = verify_images,\n        )\n        kubectl_binary(\n            name = name + \".apply\",\n            srcs = [name],\n            cluster = cluster,\n            user = user,\n            namespace = namespace,\n            tags = tags,\n            visibility = visibility,\n        )\n        kubectl_binary(\n            name = name + \".delete\",\n            srcs = [name],\n            command = \"delete\",\n            cluster = cluster,\n            push = False,\n            user = user,\n            namespace = namespace,\n            tags = tags,\n            visibility = visibility,\n        )\n        show(\n            name = name + \".show\",\n            namespace = namespace,\n            src = name,\n            tags = tags,\n            visibility = visibility,\n        )\n    else:\n        # gitops\n        if not namespace:\n            fail(\"namespace must be defined for gitops k8s_deploy\")\n        kustomization(\n            name = name,\n            namespace = namespace,\n            configmaps_srcs = configmaps_srcs,\n            secrets_srcs = secrets_srcs,\n            # disable_name_suffix_hash is renamed to configmaps_renaming in recent Kustomize\n            disable_name_suffix_hash = (configmaps_renaming != \"hash\"),\n            images = images,\n            manifests = manifests,\n            visibility = visibility,\n            substitutions = substitutions,\n            deps = deps,\n            deps_aliases = deps_aliases,\n            start_tag = start_tag,\n            end_tag = end_tag,\n            name_prefix = name_prefix,\n            name_suffix = name_suffix,\n            configurations = configurations,\n            common_labels = common_labels,\n            common_annotations = common_annotations,\n            patches = patches,\n            image_name_patches = image_name_patches,\n            image_tag_patches = image_tag_patches,\n            tags = tags,\n        )\n        kubectl_binary(\n            name = name + \".apply\",\n            srcs = [name],\n            cluster = cluster,\n            user = user,\n            namespace = namespace,\n            tags = tags,\n            visibility = visibility,\n        )\n        _gitops(\n            name = name + \".gitops\",\n            srcs = [name],\n            cluster = cluster,\n            namespace = namespace,\n            gitops_path = gitops_path,\n            strip_prefixes = [\n                namespace + \"-\",\n                cluster + \"-\",\n            ],\n            deployment_branch = deployment_branch,\n            release_branch_prefix = release_branch_prefix,\n            tags = tags,\n            visibility = [\"//visibility:public\"],\n        )\n        show(\n            name = name + \".show\",\n            src = name,\n            namespace = namespace,\n            tags = tags,\n            visibility = visibility,\n        )\n"
  },
  {
    "path": "gitops/private/k8s_gitops.sh.tpl",
    "content": "#!/usr/bin/env bash\n# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License. You may obtain a copy\n# of the License at http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software distributed under\n# the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n# OF ANY KIND, either express or implied. See the License for the specific language\n# governing permissions and limitations under the License.\n\nset -o nounset\nset -o pipefail\n\n# --- begin runfiles.bash initialization v3 ---\n# Copy-pasted from the Bazel Bash runfiles library v3.\nset -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash\n# shellcheck disable=SC1090\nsource \"${RUNFILES_DIR:-/dev/null}/$f\" 2>/dev/null || \\\nsource \"$(grep -sm1 \"^$f \" \"${RUNFILES_MANIFEST_FILE:-/dev/null}\" | cut -f2- -d' ')\" 2>/dev/null || \\\nsource \"$0.runfiles/$f\" 2>/dev/null || \\\nsource \"$(grep -sm1 \"^$f \" \"$0.runfiles_manifest\" | cut -f2- -d' ')\" 2>/dev/null || \\\nsource \"$(grep -sm1 \"^$f \" \"$0.exe.runfiles_manifest\" | cut -f2- -d' ')\" 2>/dev/null || \\\n{ echo>&2 \"ERROR: cannot find $f\"; exit 1; }; f=; set -e\n# --- end runfiles.bash initialization v3 ---\n\nrunfiles_export_envvars\n\nDEPLOYMENT_ROOT=\"\"\nPERFORM_PUSH=\"1\"\n# parse command line parameters\nwhile [[ $# -gt 0 ]]\ndo\n  key=\"$1\"\n  case $key in\n    -r|--deployment_root|--deployment-root)\n    DEPLOYMENT_ROOT=\"$2\"\n    shift # past argument\n    shift # past value\n    ;;\n    --nopush)\n    PERFORM_PUSH=\"\"\n    shift\n    ;;\n    *)    # unknown option\n    echo Unsupported parameter $1\n    exit 1\n    ;;\n  esac\ndone\n\nPIDS=()\nfunction async() {\n    # Launch the command asynchronously and track its process id.\n    \"$@\" &\n    PIDS+=($!)\n}\n\nfunction waitpids() {\n  # Wait for all of the subprocesses, returning the exit code of the first failed process.\n  if [ \"${#PIDS[@]}\" != 0 ]; then\n    for pid in ${PIDS[@]}; do\n      wait ${pid} || return $?\n    done\n  fi\n}\n\nif [ \"$PERFORM_PUSH\" == \"1\" ]; then\n  %{push_statements}\nfi\n\ncd $BUILD_WORKSPACE_DIRECTORY\n\nif [ \"%{deployment_branch}\" != \"\" -a \"${DEPLOYMENT_ROOT}\" != \"\" ] ; then\n  TARGET_DIR=${DEPLOYMENT_ROOT}\nelse\n  echo \"--deployment-root or deployment_branch is not specified, using repo root\"\n  TARGET_DIR=$BUILD_WORKSPACE_DIRECTORY\nfi\n\n# make sure that the script exits immediately if any command below fails\nset -o errexit\n%{statements}\n"
  },
  {
    "path": "gitops/private/nameprefix_deployment_labels_config.yaml",
    "content": "\nnamePrefix:\n- path: metadata/name\n- path: spec/selector/matchLabels/app\n  kind: Deployment\n- path: spec/template/metadata/labels/app\n  kind: Deployment\n- path: spec/selector/matchLabels/app.kubernetes.io\\/name\n  kind: Deployment\n- path: spec/template/metadata/labels/app.kubernetes.io\\/name\n  kind: Deployment\n- path: spec/selector/app\n  kind: Service\n- path: spec/selector/app.kubernetes.io\\/name\n  kind: Service\n"
  },
  {
    "path": "gitops/private/namesuffix_deployment_labels_config.yaml",
    "content": "\nnameSuffix:\n  - path: metadata/name\n  - path: spec/selector/matchLabels/app\n    kind: Deployment\n  - path: spec/template/metadata/labels/app\n    kind: Deployment\n  - path: spec/selector/matchLabels/app.kubernetes.io\\/name\n    kind: Deployment\n  - path: spec/template/metadata/labels/app.kubernetes.io\\/name\n    kind: Deployment\n  - path: spec/selector/app\n    kind: Service\n  - path: spec/selector/app.kubernetes.io\\/name\n    kind: Service\n"
  },
  {
    "path": "gitops/private/test/BUILD.bazel",
    "content": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\n\n# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License. You may obtain a copy\n# of the License at http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software distributed under\n# the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n# OF ANY KIND, either express or implied. See the License for the specific language\n# governing permissions and limitations under the License.\n\nload(\"//adapters:external_image.bzl\", \"external_image\")\nload(\"//gitops:defs.bzl\", \"k8s_deploy\")\nload(\"//tools:util.bzl\", \"golden_test\")\n\nlicenses([\"notice\"])  # Apache 2.0\n\nk8s_deploy(\n    name = \"deploy_test\",\n    cluster = \"testcluster\",\n    deployment_branch = \"test1\",\n    gitops = True,\n    images = [\"//gitops/private/test/images:k8s_image\"],\n    manifests = [\n        \":deployment.yaml\",\n    ],\n    namespace = \"ci\",\n    release_branch_prefix = \"gitops_test_release_branch\",\n    visibility = [\"//visibility:public\"],\n)\n\ngolden_test(\n    name = \"deploy_test_golden\",\n    in_file = \"deploy_test\",\n)\n\nexternal_image(\n    name = \"external_image\",\n    digest = \"sha:1234567890\",\n    image = \"gcr.io/repo/someimage:thetag\",\n)\n\nk8s_deploy(\n    name = \"external_image_test\",\n    cluster = \"testcluster\",\n    deployment_branch = \"test1\",\n    gitops = True,\n    images = [\n        \":external_image\",\n    ],\n    manifests = [\n        \":deployment1.yaml\",\n    ],\n    namespace = \"ci\",\n    release_branch_prefix = \"gitops_test_release_branch\",\n    visibility = [\"//visibility:public\"],\n)\n\ngolden_test(\n    name = \"external_image_golden\",\n    in_file = \"external_image_test\",\n)\n\nbzl_library(\n    name = \"templates\",\n    srcs = [\"templates.bzl\"],\n    visibility = [\"//gitops:__subpackages__\"],\n)\n"
  },
  {
    "path": "gitops/private/test/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp\nspec:\n  selector:\n    matchLabels:\n      app: myapp\n  template:\n    metadata:\n      labels:\n        app: myapp\n    spec:\n      containers:\n      - name: myapp\n        image: //gitops/private/test/images:image\n"
  },
  {
    "path": "gitops/private/test/deployment1.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp\nspec:\n  selector:\n    matchLabels:\n      app: myapp\n  template:\n    metadata:\n      labels:\n        app: myapp\n    spec:\n      containers:\n      - name: myapp\n        image: //gitops/private/test:external_image\n"
  },
  {
    "path": "gitops/private/test/goldens/deploy_test.golden",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp\n  namespace: ci\nspec:\n  selector:\n    matchLabels:\n      app: myapp\n  template:\n    metadata:\n      labels:\n        app: myapp\n    spec:\n      containers:\n      - image: localhost:15000/rules_gitops/gitops/private/test/images/helloworld@sha256:c361bf07a58f30284effb92ee1c6504b75428c5019e8a60a27120038a20e9c3e\n        name: myapp\n"
  },
  {
    "path": "gitops/private/test/goldens/external_image_test.golden",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp\n  namespace: ci\nspec:\n  selector:\n    matchLabels:\n      app: myapp\n  template:\n    metadata:\n      labels:\n        app: myapp\n    spec:\n      containers:\n      - image: gcr.io/repo/someimage@sha:1234567890\n        name: myapp\n"
  },
  {
    "path": "gitops/private/test/images/BUILD.bazel",
    "content": "load(\"@rules_gitops//adapters:rules_img.bzl\", \"k8s_push_info\")\nload(\"@rules_img//img:image.bzl\", \"image_manifest\")\nload(\"@rules_img//img:layer.bzl\", \"image_layer\")\nload(\"@rules_img//img:load.bzl\", \"image_load\")\nload(\"@rules_img//img:push.bzl\", \"image_push\")\n\npackage(\n    default_visibility = [\"//gitops/private/test:__subpackages__\"],\n)\n\nREGISTRY = \"localhost:15000\"\n\nplatform(\n    name = \"linux_amd64\",\n    constraint_values = [\n        \"@platforms//os:linux\",\n        \"@platforms//cpu:x86_64\",\n    ],\n)\n\nimage_layer(\n    name = \"layer\",\n    srcs = {\n        \"/bin/app\": \"container_content.txt\",\n    },\n)\n\nimage_manifest(\n    name = \"image\",\n    base = \"@alpine\",\n    entrypoint = [\"/bin/app\"],\n    layers = [\n        \":layer\",\n    ],\n    platform = \":linux_amd64\",\n)\n\nimage_load(\n    name = \"load\",\n    image = \":image\",\n    tag = \"helloworld:latest\",\n    visibility = [\"//:__pkg__\"],\n)\n\nimage_push(\n    name = \"push\",\n    image = \":image\",\n    registry = REGISTRY,\n    repository = \"rules_gitops/gitops/private/test/images/helloworld\",\n    tag = \"native\",\n    visibility = [\"//:__pkg__\"],\n)\n\nk8s_push_info(\n    name = \"k8s_image\",\n    image = \":image\",\n    push = \":push\",\n    registry = REGISTRY,\n    repository = \"rules_gitops/gitops/private/test/images/helloworld\",\n)\n"
  },
  {
    "path": "gitops/private/test/images/container_content.txt",
    "content": "this file goes into the test container\n\n"
  },
  {
    "path": "gitops/private/test/templates.bzl",
    "content": "# Copyright 2017 The Bazel Authors. All rights reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#    http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"Rules for templating and file layout.\"\"\"\n\ndef _expand_template_impl(ctx):\n    \"\"\"Simply spawn the template-engine in a rule.\"\"\"\n    arguments = [\n        \"--template=%s\" % ctx.file.template.path,\n        \"--output=%s\" % ctx.outputs.out.path,\n    ]\n    stamps = [ctx.file._info_file]\n    for sf in stamps:\n        arguments.append(\"--stamp_info_file=%s\" % sf.path)\n    for k in ctx.attr.substitutions:\n        arguments.append(\"--variable=%s=%s\" % (k, ctx.attr.substitutions[k]))\n    if ctx.attr.start_tag:\n        arguments.append(\"--start_tag=%s\" % ctx.attr.start_tag)\n    if ctx.attr.end_tag:\n        arguments.append(\"--end_tag=%s\" % ctx.attr.end_tag)\n    if ctx.attr.executable:\n        arguments.append(\"--executable\")\n\n    d = {\n        str(ctx.attr.deps[i].label): ctx.files.deps[i].path\n        for i in range(0, len(ctx.attr.deps))\n    }\n    arguments += [\"--imports=%s=%s\" % (k, d[k]) for k in d]\n    arguments += [\n        \"--imports=%s=%s\" % (k, d[str(ctx.label.relative(ctx.attr.deps_aliases[k]))])\n        for k in ctx.attr.deps_aliases\n    ]\n    ctx.actions.run(\n        executable = ctx.executable._engine,\n        arguments = arguments,\n        inputs = [ctx.file.template] + ctx.files.deps + stamps,\n        outputs = [ctx.outputs.out],\n        mnemonic = \"Template\",\n    )\n\nexpand_template = rule(\n    doc = \"\"\"\nExpand a template file.\n\nThis rules expands the file given in template, into the file given by out.\n\nArgs:\n  template: The template file to expand.\n  deps: additional files to expand, they will be accessible as imports[label]\n      in the template environment. If a file ends with .tpl, it is considered\n      a template itself and will be expanded.\n  deps_aliases: a dictionary of name to label. Each label in that dictionary\n      should be present in the deps attribute, and will be make accessible as\n      imports[name] in the template environment.\n  substitutions: a dictionary of key => values that will appear as variables.key\n      in the template environment.\n  out: the name of the output file to generate.\n  executable: mark the result as excutable if set to True.\n\"\"\",\n    attrs = {\n        \"out\": attr.output(mandatory = True),\n        \"deps_aliases\": attr.string_dict(default = {}),\n        \"end_tag\": attr.string(default = \"}}\"),\n        \"executable\": attr.bool(default = True),\n        # \"escape_xml\": attr.bool(default = True),\n        \"start_tag\": attr.string(default = \"{{\"),\n        \"substitutions\": attr.string_dict(mandatory = True),\n        \"template\": attr.label(\n            mandatory = True,\n            allow_single_file = True,\n        ),\n        \"deps\": attr.label_list(default = [], allow_files = True),\n        \"_engine\": attr.label(\n            default = Label(\"//templating:fast_template_engine\"),\n            executable = True,\n            cfg = \"exec\",\n        ),\n        \"_info_file\": attr.label(\n            default = Label(\"//stamper:more_stable_status.txt\"),\n            allow_single_file = True,\n        ),\n    },\n    implementation = _expand_template_impl,\n)\n\ndef strip_prefix(path, prefixes):\n    for prefix in prefixes:\n        if path.startswith(prefix):\n            return path[len(prefix):]\n    return path\n\ndef strip_suffix(path, suffixes):\n    for suffix in suffixes:\n        if path.endswith(suffix):\n            return path[:-len(suffix)]\n    return path\n\ndef _dest_path(f, strip_prefixes, strip_suffixes):\n    \"\"\"Returns the short path of f, stripped of strip_prefixes and strip_suffixes.\"\"\"\n    return strip_suffix(strip_prefix(f.short_path, strip_prefixes), strip_suffixes)\n\ndef _format_path(path_format, path):\n    dirsep = path.rfind(\"/\")\n    dirname = path[:dirsep] if dirsep > 0 else \"\"\n    basename = path[dirsep + 1:] if dirsep > 0 else path\n    extsep = basename.rfind(\".\")\n    extension = basename[extsep + 1:] if extsep > 0 else \"\"\n    basename = basename[:extsep] if extsep > 0 else basename\n    return path_format.format(\n        path = path,\n        dirname = dirname,\n        basename = basename,\n        extension = extension,\n    )\n\ndef _append_inputs(args, inputs, f, path, path_format):\n    args.append(\"--file=%s=%s\" % (\n        f.path,\n        _format_path(path_format, path),\n    ))\n    inputs.append(f)\n\ndef _merge_files_impl(ctx):\n    \"\"\"Merge a list of config files in a tar ball with the correct layout.\"\"\"\n    output = ctx.outputs.out\n    build_tar = ctx.executable._build_tar\n    inputs = []\n    args = [\n        \"--output=\" + output.path,\n        \"--directory=\" + ctx.attr.directory,\n        \"--mode=0644\",\n    ]\n    variables = [\n        \"--variable=%s=%s\" % (k, ctx.attr.substitutions[k])\n        for k in ctx.attr.substitutions\n    ]\n    for f in ctx.files.srcs:\n        path = _dest_path(f, ctx.attr.strip_prefixes, ctx.attr.strip_suffixes)\n        if path.endswith(ctx.attr.template_extension):\n            path = path[:-4]\n            f2 = ctx.actions.declare_file(ctx.label.name + \"/\" + path)\n            ctx.actions.run(\n                executable = ctx.executable._engine,\n                arguments = [\n                    \"--template=%s\" % f.path,\n                    \"--output=%s\" % f2.path,\n                    \"--noescape_xml\",\n                ] + variables,\n                inputs = [f],\n                outputs = [f2],\n            )\n            _append_inputs(args, inputs, f2, path, ctx.attr.path_format)\n        else:\n            _append_inputs(args, inputs, f, path, ctx.attr.path_format)\n    ctx.actions.run(\n        executable = build_tar,\n        arguments = args,\n        inputs = inputs,\n        outputs = [output],\n        mnemonic = \"MergeFiles\",\n    )\n\nmerge_files = rule(\n    doc = \"\"\"\nMerge a set of files in a single tarball.\n\nThis rule merge a set of files into one tarball, each file will appear in the\ntarball as a file determined by path_format, strip_prefixes and directory.\n\nOutputs:\n  <name>.tar: the tarball containing all the files in srcs.\n\nArgs:\n  srcs: The list of files to merge. If a file is ending with \".tpl\" (see\n      template_extension), it will get expanded like a template passed to\n      expand_template.\n  template_extension: extension of files to be considered as template, \".tpl\"\n      by default.\n  directory: base directory for all the files in the resulting tarball.\n  strip_prefixes: list of prefixes to strip from the path of the srcs to obtain\n      the final path (see path_format).\n  strip_suffixes: list of suffixes to strip from the path of the srcs to obtain\n      the final path (see path_format).\n  substitutions: map of substitutions to make available during template\n      expansion. Values of that map will be available as \"variables.name\" in\n      the template environment.\n  path_format: format of the final files. Each file will appear in the final\n      tarball under \"{directory}/{path_format}\" where the following string of\n      path_format are replaced:\n          {path}: path of the input file, removed from prefixes and suffixes,\n          {dirname}: directory name of path,\n          {basename}: base filename of path,\n          {extension}: extension of path\n\"\"\",\n    attrs = {\n        \"srcs\": attr.label_list(allow_files = True),\n        \"directory\": attr.string(default = \"/\"),\n        \"path_format\": attr.string(default = \"{path}\"),\n        \"strip_prefixes\": attr.string_list(default = []),\n        \"strip_suffixes\": attr.string_list(default = [\"-staging\", \"-test\"]),\n        \"substitutions\": attr.string_dict(default = {}),\n        \"template_extension\": attr.string(default = \".tpl\"),\n        \"_build_tar\": attr.label(\n            default = Label(\"@bazel_tools//tools/build_defs/pkg:build_tar\"),\n            cfg = \"exec\",\n            executable = True,\n            allow_files = True,\n        ),\n        \"_engine\": attr.label(\n            cfg = \"exec\",\n            default = Label(\"//templating:template_engine\"),\n            executable = True,\n        ),\n    },\n    outputs = {\"out\": \"%{name}.tar\"},\n    implementation = _merge_files_impl,\n)\n"
  },
  {
    "path": "go.mod",
    "content": "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.7.0\n\tgithub.com/google/go-github/v32 v32.1.0\n\tgithub.com/xanzy/go-gitlab v0.80.2\n\tgolang.org/x/oauth2 v0.5.0\n\tgoogle.golang.org/protobuf v1.36.11\n\tk8s.io/api v0.26.1\n\tk8s.io/apimachinery v0.26.1\n\tk8s.io/client-go v0.26.1\n)\n\nrequire github.com/golang/protobuf v1.5.2 // indirect\n\nrequire (\n\tal.essio.dev/pkg/shellescape v1.5.1 // indirect\n\tgithub.com/BurntSushi/toml v1.4.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/emicklei/go-restful/v3 v3.10.1 // indirect\n\tgithub.com/evanphx/json-patch/v5 v5.6.0 // indirect\n\tgithub.com/go-logr/logr v1.2.3 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.19.6 // indirect\n\tgithub.com/go-openapi/jsonreference v0.20.2 // indirect\n\tgithub.com/go-openapi/swag v0.22.3 // indirect\n\tgithub.com/gogo/protobuf v1.3.2 // indirect\n\tgithub.com/google/gnostic v0.6.9 // indirect\n\tgithub.com/google/go-querystring v1.1.0 // indirect\n\tgithub.com/google/gofuzz v1.2.0 // indirect\n\tgithub.com/hashicorp/go-cleanhttp v0.5.2 // indirect\n\tgithub.com/hashicorp/go-retryablehttp v0.7.2 // indirect\n\tgithub.com/imdario/mergo v0.3.13 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/mailru/easyjson v0.7.7 // indirect\n\tgithub.com/mattn/go-isatty v0.0.20 // indirect\n\tgithub.com/moby/spdystream v0.2.0 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.2 // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/pelletier/go-toml v1.9.5 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/spf13/cobra v1.8.0 // indirect\n\tgithub.com/spf13/pflag v1.0.5 // indirect\n\tgo.yaml.in/yaml/v3 v3.0.4 // indirect\n\tgolang.org/x/crypto v0.6.0 // indirect\n\tgolang.org/x/net v0.7.0 // indirect\n\tgolang.org/x/sys v0.6.0 // indirect\n\tgolang.org/x/term v0.5.0 // indirect\n\tgolang.org/x/text v0.7.0 // indirect\n\tgolang.org/x/time v0.3.0 // indirect\n\tgoogle.golang.org/appengine v1.6.7 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tk8s.io/klog/v2 v2.90.0 // indirect\n\tk8s.io/kube-openapi v0.0.0-20230217203603-ff9a8e8fa21d // indirect\n\tk8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 // indirect\n\tsigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect\n\tsigs.k8s.io/kind v0.31.0 // indirect\n\tsigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect\n\tsigs.k8s.io/yaml v1.4.0 // indirect\n)\n\ntool sigs.k8s.io/kind\n"
  },
  {
    "path": "go.sum",
    "content": "al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho=\nal.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=\ncloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=\ngithub.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=\ngithub.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=\ngithub.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=\ngithub.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=\ngithub.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=\ngithub.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=\ngithub.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=\ngithub.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc=\ngithub.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=\ngithub.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ=\ngithub.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=\ngithub.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=\ngithub.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=\ngithub.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0=\ngithub.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=\ngithub.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=\ngithub.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=\ngithub.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=\ngithub.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=\ngithub.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=\ngithub.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=\ngithub.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=\ngithub.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=\ngithub.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\ngithub.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0=\ngithub.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/go-github/v32 v32.1.0 h1:GWkQOdXqviCPx7Q7Fj+KyPoGm4SwHRh8rheoPhd27II=\ngithub.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=\ngithub.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=\ngithub.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=\ngithub.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=\ngithub.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=\ngithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=\ngithub.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=\ngithub.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=\ngithub.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=\ngithub.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=\ngithub.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=\ngithub.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=\ngithub.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0=\ngithub.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=\ngithub.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=\ngithub.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=\ngithub.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=\ngithub.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=\ngithub.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=\ngithub.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=\ngithub.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=\ngithub.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=\ngithub.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=\ngithub.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=\ngithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=\ngithub.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=\ngithub.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=\ngithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=\ngithub.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs=\ngithub.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo=\ngithub.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys=\ngithub.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=\ngithub.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=\ngithub.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=\ngithub.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=\ngithub.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=\ngithub.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/xanzy/go-gitlab v0.80.2 h1:CH1Q7NDklqZllox4ICVF4PwlhQGfPtE+w08Jsb74ZX0=\ngithub.com/xanzy/go-gitlab v0.80.2/go.mod h1:DlByVTSXhPsJMYL6+cm8e8fTJjeBmhrXdC/yvkKKt6M=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=\ngithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=\ngithub.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngo.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=\ngo.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=\ngo.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=\ngolang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=\ngolang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=\ngolang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=\ngolang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=\ngolang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=\ngolang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=\ngolang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=\ngolang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=\ngolang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngolang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=\ngoogle.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=\ngoogle.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=\ngoogle.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=\ngoogle.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nk8s.io/api v0.26.1 h1:f+SWYiPd/GsiWwVRz+NbFyCgvv75Pk9NK6dlkZgpCRQ=\nk8s.io/api v0.26.1/go.mod h1:xd/GBNgR0f707+ATNyPmQ1oyKSgndzXij81FzWGsejg=\nk8s.io/apimachinery v0.26.1 h1:8EZ/eGJL+hY/MYCNwhmDzVqq2lPl3N3Bo8rvweJwXUQ=\nk8s.io/apimachinery v0.26.1/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74=\nk8s.io/client-go v0.26.1 h1:87CXzYJnAMGaa/IDDfRdhTzxk/wzGZ+/HUQpqgVSZXU=\nk8s.io/client-go v0.26.1/go.mod h1:IWNSglg+rQ3OcvDkhY6+QLeasV4OYHDjdqeWkDQZwGE=\nk8s.io/klog/v2 v2.90.0 h1:VkTxIV/FjRXn1fgNNcKGM8cfmL1Z33ZjXRTVxKCoF5M=\nk8s.io/klog/v2 v2.90.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=\nk8s.io/kube-openapi v0.0.0-20230217203603-ff9a8e8fa21d h1:oFDpQ7FfzinCtrFOl4izwOWsdTprlS2A9IXBENMW0UA=\nk8s.io/kube-openapi v0.0.0-20230217203603-ff9a8e8fa21d/go.mod h1:/BYxry62FuDzmI+i9B+X2pqfySRmSOW2ARmj5Zbqhj0=\nk8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5 h1:kmDqav+P+/5e1i9tFfHq1qcF3sOrDp+YEkVDAHu7Jwk=\nk8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=\nsigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=\nsigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=\nsigs.k8s.io/kind v0.31.0 h1:UcT4nzm+YM7YEbqiAKECk+b6dsvc/HRZZu9U0FolL1g=\nsigs.k8s.io/kind v0.31.0/go.mod h1:FSqriGaoTPruiXWfRnUXNykF8r2t+fHtK0P0m1AbGF8=\nsigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=\nsigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=\nsigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=\nsigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=\n"
  },
  {
    "path": "kubectl/BUILD.bazel",
    "content": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\nload(\":defs.bzl\", \"resolved_toolchain\")\n\ntoolchain_type(\n    name = \"toolchain_type\",\n    visibility = [\"//visibility:public\"],\n)\n\nresolved_toolchain(\n    name = \"resolved_toolchain\",\n    visibility = [\"//visibility:public\"],\n)\n\nbzl_library(\n    name = \"defs\",\n    srcs = [\"defs.bzl\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//kubectl/private:extension\",\n        \"//kubectl/private:kubectl_binary\",\n        \"//kubectl/private:resolved_toolchain\",\n        \"//kubectl/private:toolchain\",\n    ],\n)\n"
  },
  {
    "path": "kubectl/defs.bzl",
    "content": "\"\"\"Public API for kubectl rules.\"\"\"\n\nload(\"//kubectl/private:extension.bzl\", _kubectl = \"kubectl\")\nload(\"//kubectl/private:kubectl_binary.bzl\", _kubectl_binary = \"kubectl_binary\")\nload(\"//kubectl/private:resolved_toolchain.bzl\", _resolved_toolchain = \"resolved_toolchain\")\nload(\"//kubectl/private:toolchain.bzl\", _kubectl_toolchain = \"kubectl_toolchain\")\n\nkubectl = _kubectl\nkubectl_binary = _kubectl_binary\nkubectl_toolchain = _kubectl_toolchain\nresolved_toolchain = _resolved_toolchain\n"
  },
  {
    "path": "kubectl/private/BUILD.bazel",
    "content": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\n\npackage(\n    default_visibility = [\"//kubectl:__subpackages__\"],\n)\n\nexports_files([\n    \"run-all.sh.tpl\",\n])\n\nbzl_library(\n    name = \"extension\",\n    srcs = [\"extension.bzl\"],\n    deps = [\n        \":platforms\",\n        \"//kubectl/private/versions\",\n        \"@bazel_tools//tools/build_defs/repo:cache.bzl\",\n        \"@bazel_tools//tools/build_defs/repo:http.bzl\",\n        \"@bazel_tools//tools/build_defs/repo:utils.bzl\",\n    ],\n)\n\nbzl_library(\n    name = \"toolchain\",\n    srcs = [\"toolchain.bzl\"],\n    deps = [\":providers\"],\n)\n\nbzl_library(\n    name = \"kubectl_binary\",\n    srcs = [\"kubectl_binary.bzl\"],\n    deps = [\n        \"//adapters:providers\",\n        \"//kustomize:defs\",\n        \"//stamper:stamp\",\n    ],\n)\n\nbzl_library(\n    name = \"kubeconfig\",\n    srcs = [\"kubeconfig.bzl\"],\n)\n\nbzl_library(\n    name = \"platforms\",\n    srcs = [\"platforms.bzl\"],\n)\n\nbzl_library(\n    name = \"providers\",\n    srcs = [\"providers.bzl\"],\n)\n\nbzl_library(\n    name = \"resolved_toolchain\",\n    srcs = [\"resolved_toolchain.bzl\"],\n)\n"
  },
  {
    "path": "kubectl/private/extension.bzl",
    "content": "\"\"\"Module extension for kubectl toolchain.\"\"\"\n\nload(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_file\")\nload(\"//kubectl/private:platforms.bzl\", \"PLATFORMS\")\nload(\"//kubectl/private/versions:versions.bzl\", \"LATEST_KUBECTL_VERSION\", \"VERSIONS\")\n\ndef _kubectl_hub_impl(rctx):\n    kubectl_hub_build_content = \"\"\"\npackage(\n    default_visibility = [\"//visibility:public\"]\n)\n\"\"\"\n    toolchains_build_content = \"\"\"load(\"@rules_gitops//kubectl:defs.bzl\", \"kubectl_toolchain\")\"\"\"\n\n    for [platform, meta] in PLATFORMS.items():\n        toolchains_build_content += \"\"\"\nkubectl_toolchain(\n    name = \"kubectl_{platform}_toolchain\",\n    executable = \"@kubectl_{platform}//file\"\n)\n\ntoolchain(\n    name = \"{platform}_toolchain\",\n    exec_compatible_with = {compatible_with},\n    toolchain = \":kubectl_{platform}_toolchain\",\n    toolchain_type = \"@rules_gitops//kubectl:toolchain_type\",\n)\n\"\"\".format(\n            platform = platform,\n            compatible_with = meta.compatible_with,\n        )\n\n    rctx.file(\"toolchains/BUILD.bazel\", content = toolchains_build_content)\n    rctx.file(\"BUILD.bazel\", content = kubectl_hub_build_content)\n\n_kubectl_hub = repository_rule(\n    implementation = _kubectl_hub_impl,\n    attrs = {},\n)\n\nKUBECTL_TOOLCHAIN_BUILD = \"\"\"\nkubectl_toolchain(\n    name = \"kubectl_toolchain\",\n    executable = \"file\"\n)\n\"\"\"\n\ndef _kubectl_extension_impl(module_ctx):\n    kubectl_version = LATEST_KUBECTL_VERSION\n\n    for mod in module_ctx.modules:\n        if len(mod.tags.toolchain) > 1:\n            fail(\"Expected kubectl toolchain to only be declared once\")\n\n        # Allow the root module to override the kubectl toolchain version\n        if mod.is_root and len(mod.tags.toolchain) == 1:\n            kubectl_version = mod.tags.toolchain[0].version\n\n    if not VERSIONS[kubectl_version]:\n        fail(\"No matching version found for kubectl v{}\".format(kubectl_version))\n\n    binaries = VERSIONS[kubectl_version]\n\n    for [platform, sha256] in binaries.items():\n        [os, arch] = platform.split(\"_\")\n        http_file(\n            name = \"kubectl_{}\".format(platform),\n            urls = [\n                \"https://dl.k8s.io/release/v{version}/bin/{os}/{arch}/kubectl\".format(version = kubectl_version, os = os, arch = arch),\n            ],\n            executable = True,\n            sha256 = sha256,\n        )\n\n    _kubectl_hub(\n        name = \"kubectl\",\n    )\n\n    return module_ctx.extension_metadata()\n\nkubectl = module_extension(\n    implementation = _kubectl_extension_impl,\n    tag_classes = {\n        \"toolchain\": tag_class(\n            attrs = {\n                \"version\": attr.string(\n                    doc = \"kubectl binary version\",\n                ),\n            },\n        ),\n    },\n)\n"
  },
  {
    "path": "kubectl/private/kubeconfig.bzl",
    "content": "\"\"\"Repository rule for configuring kubectl and kubeconfig.\"\"\"\n\ndef _kubectl_config(repository_ctx, args):\n    kubectl = repository_ctx.path(\"kubectl\")\n    kubeconfig_yaml = repository_ctx.path(\"kubeconfig\")\n    exec_result = repository_ctx.execute(\n        [kubectl, \"--kubeconfig\", kubeconfig_yaml, \"config\"] + args,\n        environment = {\n            # prevent kubectl config to stumble on shared .kube/config.lock file\n            \"HOME\": str(repository_ctx.path(\".\")),\n        },\n        quiet = True,\n    )\n    if exec_result.return_code != 0:\n        fail(\"Error executing kubectl config %s\" % \" \".join(args))\n\ndef _kubeconfig_impl(repository_ctx):\n    \"\"\"Find local kubernetes certificates\"\"\"\n\n    # find and symlink kubectl\n    kubectl = repository_ctx.which(\"kubectl\")\n    if not kubectl:\n        fail(\"Unable to find kubectl executable. PATH=%s\" % repository_ctx.path)\n    repository_ctx.symlink(kubectl, \"kubectl\")\n    repository_ctx.file(repository_ctx.path(\"cluster\"), content = repository_ctx.attr.cluster, executable = False)\n\n    # TODO: figure out how to use BUILD_USER\n    if \"USER\" in repository_ctx.os.environ:\n        user = repository_ctx.os.environ[\"USER\"]\n    else:\n        exec_result = repository_ctx.execute([\"whoami\"])\n        if exec_result.return_code != 0:\n            fail(\"Error detecting current user\")\n        user = exec_result.stdout.rstrip()\n    token = None\n    ca_crt = None\n    kubecert_cert = None\n    kubecert_key = None\n    server = repository_ctx.attr.server\n\n    # check service account first\n    serviceaccount = repository_ctx.path(\"/var/run/secrets/kubernetes.io/serviceaccount\")\n    if serviceaccount.exists:\n        ca_crt = \"/var/run/secrets/kubernetes.io/serviceaccount/ca.crt\"\n        token_file = serviceaccount.get_child(\"token\")\n        if token_file.exists:\n            exec_result = repository_ctx.execute([\"cat\", token_file.realpath])\n            if exec_result.return_code != 0:\n                fail(\"Error reading user token\")\n            token = exec_result.stdout.rstrip()\n\n        # use master url from the environemnt\n        if \"KUBERNETES_SERVICE_HOST\" in repository_ctx.os.environ:\n            server = \"https://%s:%s\" % (\n                repository_ctx.os.environ[\"KUBERNETES_SERVICE_HOST\"],\n                repository_ctx.os.environ[\"KUBERNETES_SERVICE_PORT\"],\n            )\n        else:\n            # fall back to the default\n            server = \"https://kubernetes.default\"\n    elif repository_ctx.attr.use_host_config:\n        home = repository_ctx.path(repository_ctx.os.environ[\"HOME\"])\n        kubeconfig = home.get_child(\".kube\").get_child(\"config\")\n        if repository_ctx.path(kubeconfig).exists:\n            repository_ctx.symlink(kubeconfig, repository_ctx.path(\"kubeconfig\"))\n        else:\n            _kubectl_config(repository_ctx, [\n                \"set-cluster\",\n                repository_ctx.attr.cluster,\n                \"--server\",\n                server,\n            ])\n    else:\n        home = repository_ctx.path(repository_ctx.os.environ[\"HOME\"])\n        certs = home.get_child(\".kube\").get_child(\"certs\")\n        ca_crt = certs.get_child(\"ca.crt\").realpath\n        kubecert_cert = certs.get_child(\"kubecert.cert\")\n        kubecert_key = certs.get_child(\"kubecert.key\")\n\n    # config set-cluster {cluster} \\\n    #     --certificate-authority=... \\\n    #     --server=https://dev3.k8s.tubemogul.info:443 \\\n    #     --embed-certs\",\n    if ca_crt:\n        _kubectl_config(repository_ctx, [\n            \"set-cluster\",\n            repository_ctx.attr.cluster,\n            \"--server\",\n            server,\n            \"--certificate-authority\",\n            ca_crt,\n        ])\n\n    # config set-credentials {user} --token=...\",\n    if token:\n        _kubectl_config(repository_ctx, [\n            \"set-credentials\",\n            user,\n            \"--token\",\n            token,\n        ])\n\n    # config set-credentials {user} --client-certificate=... --embed-certs\",\n    if kubecert_cert and kubecert_cert.exists:\n        _kubectl_config(repository_ctx, [\n            \"set-credentials\",\n            user,\n            \"--client-certificate\",\n            kubecert_cert.realpath,\n        ])\n\n    # config set-credentials {user} --client-key=... --embed-certs\",\n    if kubecert_key and kubecert_key.exists:\n        _kubectl_config(repository_ctx, [\n            \"set-credentials\",\n            user,\n            \"--client-key\",\n            kubecert_key.realpath,\n        ])\n\n    # export repostory contents\n    repository_ctx.file(\"BUILD\", \"\"\"exports_files([\"kubeconfig\", \"kubectl\", \"cluster\"])\"\"\", False)\n\n    return {\n        \"name\": repository_ctx.attr.name,\n        \"cluster\": repository_ctx.attr.cluster,\n        \"server\": repository_ctx.attr.server,\n        \"use_host_config\": repository_ctx.attr.use_host_config,\n    }\n\nkubeconfig = repository_rule(\n    attrs = {\n        \"cluster\": attr.string(),\n        \"server\": attr.string(),\n        \"use_host_config\": attr.bool(),\n    },\n    environ = [\n        \"HOME\",\n        \"USER\",\n        \"KUBERNETES_SERVICE_HOST\",\n        \"KUBERNETES_SERVICE_PORT\",\n    ],\n    local = True,\n    implementation = _kubeconfig_impl,\n)\n"
  },
  {
    "path": "kubectl/private/kubectl_binary.bzl",
    "content": "\"\"\"\nSimple rule for running kubectl from the toolchain config\n\"\"\"\n\nload(\"//adapters:providers.bzl\", \"K8sPushInfo\")\nload(\"//kustomize:defs.bzl\", \"KustomizeInfo\")\nload(\"//stamper:stamp.bzl\", \"stamp\")\n\ndef _kubectl_binary_impl(ctx):\n    executable = ctx.toolchains[\"@rules_gitops//kubectl:toolchain_type\"].kubectlinfo.executable\n\n    runfiles = ctx.runfiles(\n        files = ctx.files.srcs + [ctx.executable._template_engine, ctx.file._info_file, executable],\n    ).merge(ctx.attr._bash_runfiles[DefaultInfo].default_runfiles)\n\n    files = []\n\n    cluster_arg = ctx.attr.cluster\n    cluster_arg = ctx.expand_make_variables(\"cluster\", cluster_arg, {})\n    if \"{\" in ctx.attr.cluster:\n        cluster_arg, cluster_files = stamp(ctx, cluster_arg, ctx.files.srcs, ctx.label.name + \".cluster-name\")\n        files.extend(cluster_files)\n\n    user_arg = ctx.attr.user\n    user_arg = ctx.expand_make_variables(\"user\", user_arg, {})\n    if \"{\" in ctx.attr.user:\n        user_arg, user_files = stamp(ctx, user_arg, ctx.files.srcs, ctx.label.name + \".user-name\")\n        files.extend(user_files)\n\n    kubectl_command_arg = ctx.attr.command\n    kubectl_command_arg = ctx.expand_make_variables(\"kubectl_command\", kubectl_command_arg, {})\n\n    statements = \"\"\n    transitive_runfiles = []\n\n    if ctx.attr.push:\n        trans_img_pushes = depset(transitive = [obj[KustomizeInfo].image_pushes for obj in ctx.attr.srcs]).to_list()\n        statements += \"\\n\".join([\n            \"# {}\\n\".format(exe[K8sPushInfo].image_label) +\n            \"echo  pushing {}/{}\".format(exe[K8sPushInfo].registry, exe[K8sPushInfo].repository)\n            for exe in trans_img_pushes\n            if hasattr(exe[K8sPushInfo], \"pusher\")\n        ]) + \"\\n\"\n        statements += \"\\n\".join([\n            \"async \\\"%s\\\"\" % exe[K8sPushInfo].pusher.files_to_run.executable.short_path\n            for exe in trans_img_pushes\n            if hasattr(exe[K8sPushInfo], \"pusher\")\n        ]) + \"\\nwaitpids\\n\"\n\n        transitive_runfiles += [exe[K8sPushInfo].pusher.default_runfiles for exe in trans_img_pushes if hasattr(exe[K8sPushInfo], \"pusher\")]\n\n    runfiles = runfiles.merge_all(transitive_runfiles)\n\n    namespace = ctx.attr.namespace\n    for inattr in ctx.attr.srcs:\n        for infile in inattr.files.to_list():\n            statements += \"{template_engine} --template={infile} --variable=NAMESPACE={namespace} --stamp_info_file={info_file} | \\\"{kubectl}\\\" --cluster=\\\"{cluster}\\\" --user=\\\"{user}\\\" {kubectl_command} -f -\\n\".format(\n                kubectl = executable.short_path,\n                infile = infile.short_path,\n                cluster = cluster_arg,\n                user = user_arg,\n                kubectl_command = kubectl_command_arg,\n                template_engine = ctx.executable._template_engine.short_path,\n                namespace = namespace,\n                info_file = ctx.file._info_file.short_path,\n            )\n\n    ctx.actions.expand_template(\n        template = ctx.file._template,\n        substitutions = {\n            \"%{statements}\": statements,\n        },\n        output = ctx.outputs.executable,\n    )\n\n    files.append(ctx.outputs.executable)\n\n    return [\n        DefaultInfo(files = depset(files), runfiles = runfiles, executable = ctx.outputs.executable),\n    ]\n\nkubectl_binary = rule(\n    attrs = {\n        \"srcs\": attr.label_list(providers = [KustomizeInfo]),\n        \"cluster\": attr.string(mandatory = True),\n        \"namespace\": attr.string(mandatory = True),\n        \"command\": attr.string(default = \"apply\"),\n        \"user\": attr.string(default = \"{BUILD_USER}\"),\n        \"push\": attr.bool(default = True),\n        \"_build_user_value\": attr.label(\n            default = Label(\"//stamper:build_user_value.txt\"),\n            allow_single_file = True,\n        ),\n        \"_info_file\": attr.label(\n            default = Label(\"//stamper:more_stable_status.txt\"),\n            allow_single_file = True,\n        ),\n        \"_stamper\": attr.label(\n            default = Label(\"//stamper:stamper\"),\n            cfg = \"exec\",\n            executable = True,\n            allow_files = True,\n        ),\n        \"_template\": attr.label(\n            default = Label(\"//kubectl/private:run-all.sh.tpl\"),\n            allow_single_file = True,\n        ),\n        \"_template_engine\": attr.label(\n            default = Label(\"//templating:fast_template_engine\"),\n            executable = True,\n            cfg = \"exec\",\n        ),\n        \"_bash_runfiles\": attr.label(\n            default = Label(\"@bazel_tools//tools/bash/runfiles\"),\n        ),\n    },\n    executable = True,\n    implementation = _kubectl_binary_impl,\n    toolchains = [\"@rules_gitops//kubectl:toolchain_type\"],\n)\n"
  },
  {
    "path": "kubectl/private/platforms.bzl",
    "content": "\"\"\"Platform definitions for kubectl toolchain.\"\"\"\n\nPLATFORMS = {\n    \"darwin_amd64\": struct(\n        compatible_with = [\n            \"@platforms//os:macos\",\n            \"@platforms//cpu:x86_64\",\n        ],\n    ),\n    \"darwin_arm64\": struct(\n        compatible_with = [\n            \"@platforms//os:macos\",\n            \"@platforms//cpu:arm64\",\n        ],\n    ),\n    \"linux_amd64\": struct(\n        compatible_with = [\n            \"@platforms//os:linux\",\n            \"@platforms//cpu:x86_64\",\n        ],\n    ),\n    \"linux_arm64\": struct(\n        compatible_with = [\n            \"@platforms//os:linux\",\n            \"@platforms//cpu:x86_64\",\n        ],\n    ),\n}\n"
  },
  {
    "path": "kubectl/private/providers.bzl",
    "content": "\"\"\"Provider definitions for kubectl toolchain.\"\"\"\n\nKubectlToolchainInfo = provider(\n    doc = \"Toolchain information about the kubectl executable\",\n    fields = {\n        \"target_tool_path\": \"Path to the kubectl executable for the target platform.\",\n        \"executable\": \"Hermetically download toolchain executable file\",\n    },\n)\n"
  },
  {
    "path": "kubectl/private/resolved_toolchain.bzl",
    "content": "\"\"\"This module implements an alias rule to the resolved toolchain.\n\"\"\"\n\nDOC = \"\"\"\\\nExposes a concrete toolchain which is the result of Bazel resolving the\ntoolchain for the execution or target platform.\nWorkaround for https://github.com/bazelbuild/bazel/issues/14009\n\"\"\"\n\n# Forward all the providers\ndef _resolved_toolchain_impl(ctx):\n    toolchain_info = ctx.toolchains[\"//kubectl:toolchain_type\"]\n    return [\n        toolchain_info,\n        toolchain_info.default,\n        toolchain_info.kubectlinfo,\n        toolchain_info.template_variables,\n    ]\n\n# Copied from java_toolchain_alias\n# https://cs.opensource.google/bazel/bazel/+/master:tools/jdk/java_toolchain_alias.bzl\nresolved_toolchain = rule(\n    implementation = _resolved_toolchain_impl,\n    toolchains = [\"//kubectl:toolchain_type\"],\n    doc = DOC,\n)\n"
  },
  {
    "path": "kubectl/private/run-all.sh.tpl",
    "content": "#!/usr/bin/env bash\n\nset -eu\n\n# --- begin runfiles.bash initialization v3 ---\n# Copy-pasted from the Bazel Bash runfiles library v3.\nset -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash\n# shellcheck disable=SC1090\nsource \"${RUNFILES_DIR:-/dev/null}/$f\" 2>/dev/null || \\\nsource \"$(grep -sm1 \"^$f \" \"${RUNFILES_MANIFEST_FILE:-/dev/null}\" | cut -f2- -d' ')\" 2>/dev/null || \\\nsource \"$0.runfiles/$f\" 2>/dev/null || \\\nsource \"$(grep -sm1 \"^$f \" \"$0.runfiles_manifest\" | cut -f2- -d' ')\" 2>/dev/null || \\\nsource \"$(grep -sm1 \"^$f \" \"$0.exe.runfiles_manifest\" | cut -f2- -d' ')\" 2>/dev/null || \\\n{ echo>&2 \"ERROR: cannot find $f\"; exit 1; }; f=; set -e\n# --- end runfiles.bash initialization v3 ---\n\nPIDS=()\nfunction async() {\n    # Launch the command asynchronously and track its process id.\n    \"./$@\" &\n    PIDS+=($!)\n}\n\nfunction waitpids() {\n    # Wait for all of the subprocesses, failing the script if any of them failed.\n    if [ \"${#PIDS[@]}\" != 0 ]; then\n        for pid in ${PIDS[@]}; do\n            wait ${pid}\n        done\n    fi\n}\n\n%{statements}\n\n"
  },
  {
    "path": "kubectl/private/toolchain.bzl",
    "content": "\"Kubectl toolchain rule\"\n\nload(\"//kubectl/private:providers.bzl\", \"KubectlToolchainInfo\")\n\n# Avoid using non-normalized paths (workspace/../other_workspace/path)\ndef _to_manifest_path(ctx, file):\n    if file.short_path.startswith(\"../\"):\n        return \"external/\" + file.short_path[3:]\n    else:\n        return ctx.workspace_name + \"/\" + file.short_path\n\ndef _kubectl_toolchain_impl(ctx):\n    executable = ctx.file.executable\n    target_tool_path = _to_manifest_path(ctx, executable)\n\n    template_variables = platform_common.TemplateVariableInfo({\n        \"KUBECTL_BIN\": target_tool_path,\n    })\n\n    default = DefaultInfo(\n        files = depset([executable]),\n        runfiles = ctx.runfiles(files = [executable]),\n    )\n    kubectlinfo = KubectlToolchainInfo(\n        target_tool_path = target_tool_path,\n        executable = executable,\n    )\n    toolchain_info = platform_common.ToolchainInfo(\n        kubectlinfo = kubectlinfo,\n        template_variables = template_variables,\n        default = default,\n    )\n    return [\n        default,\n        toolchain_info,\n        template_variables,\n    ]\n\nkubectl_toolchain = rule(\n    implementation = _kubectl_toolchain_impl,\n    attrs = {\n        \"executable\": attr.label(\n            doc = \"A hermetically downloaded executable target for the target platform.\",\n            mandatory = True,\n            allow_single_file = True,\n        ),\n    },\n)\n"
  },
  {
    "path": "kubectl/private/versions/BUILD.bazel",
    "content": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\nload(\"@rules_go//go:def.bzl\", \"go_binary\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"update_versions.go\"],\n    importpath = \"github.com/adobe/rules_gitops/kubectl/private/versions\",\n    visibility = [\"//visibility:private\"],\n)\n\ngo_binary(\n    name = \"update_versions\",\n    embed = [\":go_default_library\"],\n    visibility = [\"//visibility:public\"],\n)\n\nbzl_library(\n    name = \"versions\",\n    srcs = [\"versions.bzl\"],\n    visibility = [\"//kubectl:__subpackages__\"],\n)\n"
  },
  {
    "path": "kubectl/private/versions/update_versions.go",
    "content": "// update_versions downloads kubectl release information from dl.k8s.io\n// and generates a versions.bzl file with SHA256 digests for each platform.\n//\n// Usage:\n//\n//\tgo run update_versions.go\n//\n// The script expects BUILD_WORKSPACE_DIRECTORY to be set, or writes to\n// the default location relative to this file's directory.\npackage main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sort\"\n\t\"strings\"\n\t\"sync\"\n)\n\nconst (\n\tdlBaseURL  = \"https://dl.k8s.io/release/\"\n\toutputFile = \"kubectl/private/versions/versions.bzl\"\n)\n\n// Platform combinations to fetch\nvar platforms = []struct {\n\tos   string\n\tarch string\n}{\n\t{\"darwin\", \"amd64\"},\n\t{\"darwin\", \"arm64\"},\n\t{\"linux\", \"amd64\"},\n\t{\"linux\", \"arm64\"},\n}\n\n// VersionInfo holds platform -> sha256 mappings for a version\ntype VersionInfo struct {\n\tPlatforms map[string]string // platform (e.g., \"darwin_arm64\") -> sha256\n}\n\nfunc main() {\n\tlog.SetFlags(0)\n\n\t// Discover stable versions\n\tversions := discoverStableVersions()\n\tif len(versions) == 0 {\n\t\tlog.Fatalf(\"No stable versions found\")\n\t}\n\n\tlog.Printf(\"Found %d stable versions: %v\", len(versions), versions)\n\n\t// Fetch SHA256 checksums for each version and platform\n\tversionInfos := fetchAllChecksums(versions)\n\n\t// Find latest version\n\tlatestVersion := findLatestVersion(versions)\n\n\t// Generate versions.bzl content\n\tcontent := generateBzl(versionInfos, latestVersion)\n\n\t// Determine output path\n\toutputPath := getOutputPath()\n\n\t// Write the file\n\tif err := os.MkdirAll(filepath.Dir(outputPath), 0755); err != nil {\n\t\tlog.Fatalf(\"Failed to create directory: %v\", err)\n\t}\n\tif err := os.WriteFile(outputPath, []byte(content), 0644); err != nil {\n\t\tlog.Fatalf(\"Failed to write file: %v\", err)\n\t}\n\n\tlog.Printf(\"Successfully wrote %s with %d versions\", outputPath, len(versionInfos))\n}\n\n// discoverStableVersions probes stable-X.Y.txt endpoints to find available versions\nfunc discoverStableVersions() []string {\n\tvar versions []string\n\n\t// First, get the absolute latest stable to determine the max minor version\n\tlatestStable := fetchStableVersion(\"stable.txt\")\n\tif latestStable == \"\" {\n\t\tlog.Printf(\"Warning: could not fetch stable.txt\")\n\t\treturn versions\n\t}\n\tversions = append(versions, latestStable)\n\n\t// Parse the minor version from latest stable (e.g., \"1.35.0\" -> 35)\n\tmaxMinor := parseMinorVersion(latestStable)\n\tif maxMinor == 0 {\n\t\tlog.Printf(\"Warning: could not parse minor version from %s\", latestStable)\n\t\treturn versions\n\t}\n\n\tlog.Printf(\"Latest stable is %s (minor version %d)\", latestStable, maxMinor)\n\n\t// Probe stable-1.X.txt for minor versions from 20 up to the latest\n\t// Kubernetes typically maintains ~3-4 minor versions, but we fetch more for completeness\n\tfor minor := 20; minor <= maxMinor; minor++ {\n\t\tfilename := fmt.Sprintf(\"stable-1.%d.txt\", minor)\n\t\tif v := fetchStableVersion(filename); v != \"\" {\n\t\t\t// Avoid duplicates\n\t\t\tfound := false\n\t\t\tfor _, existing := range versions {\n\t\t\t\tif existing == v {\n\t\t\t\t\tfound = true\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t\tif !found {\n\t\t\t\tversions = append(versions, v)\n\t\t\t}\n\t\t}\n\t}\n\n\treturn versions\n}\n\n// parseMinorVersion extracts the minor version number from a version string like \"1.35.0\"\nfunc parseMinorVersion(version string) int {\n\tparts := strings.Split(version, \".\")\n\tif len(parts) < 2 {\n\t\treturn 0\n\t}\n\tvar minor int\n\tfmt.Sscanf(parts[1], \"%d\", &minor)\n\treturn minor\n}\n\n// fetchStableVersion fetches a stable version string from dl.k8s.io\nfunc fetchStableVersion(filename string) string {\n\turl := dlBaseURL + filename\n\n\tresp, err := http.Get(url)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\"\n\t}\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn \"\"\n\t}\n\n\tversion := strings.TrimSpace(string(body))\n\t// Validate it looks like a version (starts with 'v')\n\tif !strings.HasPrefix(version, \"v\") {\n\t\treturn \"\"\n\t}\n\n\t// Strip the 'v' prefix for consistency\n\treturn strings.TrimPrefix(version, \"v\")\n}\n\n// fetchAllChecksums fetches SHA256 checksums for all versions and platforms\nfunc fetchAllChecksums(versions []string) map[string]*VersionInfo {\n\tresult := make(map[string]*VersionInfo)\n\tvar mu sync.Mutex\n\tvar wg sync.WaitGroup\n\n\t// Use a semaphore to limit concurrent requests\n\tsem := make(chan struct{}, 10)\n\n\tfor _, version := range versions {\n\t\tfor _, p := range platforms {\n\t\t\twg.Add(1)\n\t\t\tgo func(version, os, arch string) {\n\t\t\t\tdefer wg.Done()\n\t\t\t\tsem <- struct{}{}\n\t\t\t\tdefer func() { <-sem }()\n\n\t\t\t\tsha256, err := fetchSHA256(version, os, arch)\n\t\t\t\tif err != nil {\n\t\t\t\t\tlog.Printf(\"Warning: failed to fetch sha256 for %s %s/%s: %v\", version, os, arch, err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tplatform := fmt.Sprintf(\"%s_%s\", os, arch)\n\n\t\t\t\tmu.Lock()\n\t\t\t\tif result[version] == nil {\n\t\t\t\t\tresult[version] = &VersionInfo{\n\t\t\t\t\t\tPlatforms: make(map[string]string),\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tresult[version].Platforms[platform] = sha256\n\t\t\t\tmu.Unlock()\n\t\t\t}(version, p.os, p.arch)\n\t\t}\n\t}\n\n\twg.Wait()\n\treturn result\n}\n\n// fetchSHA256 fetches the SHA256 checksum for a specific version/os/arch\nfunc fetchSHA256(version, os, arch string) (string, error) {\n\turl := fmt.Sprintf(\"%sv%s/bin/%s/%s/kubectl.sha256\", dlBaseURL, version, os, arch)\n\n\tresp, err := http.Get(url)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"HTTP %d\", resp.StatusCode)\n\t}\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// The sha256 file may contain just the hash, or \"hash  filename\" format\n\tcontent := strings.TrimSpace(string(body))\n\tparts := strings.Fields(content)\n\tif len(parts) == 0 {\n\t\treturn \"\", fmt.Errorf(\"empty sha256 file\")\n\t}\n\n\tsha256 := parts[0]\n\n\t// Validate it looks like a SHA256 (64 hex chars)\n\tif len(sha256) != 64 {\n\t\treturn \"\", fmt.Errorf(\"invalid sha256 length: %d\", len(sha256))\n\t}\n\n\treturn sha256, nil\n}\n\n// findLatestVersion returns the latest version from a list of versions\nfunc findLatestVersion(versions []string) string {\n\tif len(versions) == 0 {\n\t\treturn \"\"\n\t}\n\n\tlatest := versions[0]\n\tfor _, v := range versions[1:] {\n\t\tif compareVersions(v, latest) > 0 {\n\t\t\tlatest = v\n\t\t}\n\t}\n\treturn latest\n}\n\n// compareVersions compares two semver-like version strings\n// Returns positive if v1 > v2, negative if v1 < v2, zero if equal\nfunc compareVersions(v1, v2 string) int {\n\tif v2 == \"\" {\n\t\treturn 1\n\t}\n\n\tparts1 := strings.Split(v1, \".\")\n\tparts2 := strings.Split(v2, \".\")\n\n\tfor i := 0; i < len(parts1) && i < len(parts2); i++ {\n\t\tvar n1, n2 int\n\t\tfmt.Sscanf(parts1[i], \"%d\", &n1)\n\t\tfmt.Sscanf(parts2[i], \"%d\", &n2)\n\t\tif n1 != n2 {\n\t\t\treturn n1 - n2\n\t\t}\n\t}\n\treturn len(parts1) - len(parts2)\n}\n\nfunc generateBzl(versions map[string]*VersionInfo, latestVersion string) string {\n\tvar sb strings.Builder\n\n\t// Sort versions for consistent output (reverse to put newer versions first)\n\tversionList := make([]string, 0, len(versions))\n\tfor v := range versions {\n\t\tversionList = append(versionList, v)\n\t}\n\tsort.Slice(versionList, func(i, j int) bool {\n\t\treturn compareVersions(versionList[i], versionList[j]) > 0\n\t})\n\n\tsb.WriteString(`\"\"\"Generated by update_versions.go - do not edit manually.\"\"\"\n\n`)\n\tsb.WriteString(fmt.Sprintf(\"LATEST_KUBECTL_VERSION = %q\\n\\n\", latestVersion))\n\tsb.WriteString(\"VERSIONS = {\\n\")\n\n\tfor _, version := range versionList {\n\t\tinfo := versions[version]\n\t\tsb.WriteString(fmt.Sprintf(\"    %q: {\\n\", version))\n\n\t\t// Sort platforms for consistent output\n\t\tplatformKeys := make([]string, 0, len(info.Platforms))\n\t\tfor p := range info.Platforms {\n\t\t\tplatformKeys = append(platformKeys, p)\n\t\t}\n\t\tsort.Strings(platformKeys)\n\n\t\tfor _, platform := range platformKeys {\n\t\t\tsha256 := info.Platforms[platform]\n\t\t\tsb.WriteString(fmt.Sprintf(\"        %q: %q,\\n\", platform, sha256))\n\t\t}\n\t\tsb.WriteString(\"    },\\n\")\n\t}\n\n\tsb.WriteString(\"}\\n\")\n\n\treturn sb.String()\n}\n\nfunc getOutputPath() string {\n\t// Check for BUILD_WORKSPACE_DIRECTORY environment variable\n\tif wsDir := os.Getenv(\"BUILD_WORKSPACE_DIRECTORY\"); wsDir != \"\" {\n\t\treturn filepath.Join(wsDir, outputFile)\n\t}\n\n\t// Fallback: find workspace root by looking for MODULE.bazel or WORKSPACE\n\t// Start from executable directory and walk up\n\texePath, err := os.Executable()\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to get executable path: %v\", err)\n\t}\n\tdir := filepath.Dir(exePath)\n\n\t// Also try current working directory\n\tcwd, _ := os.Getwd()\n\tfor _, startDir := range []string{dir, cwd} {\n\t\td := startDir\n\t\tfor d != \"/\" && d != \".\" {\n\t\t\tif _, err := os.Stat(filepath.Join(d, \"MODULE.bazel\")); err == nil {\n\t\t\t\treturn filepath.Join(d, outputFile)\n\t\t\t}\n\t\t\tif _, err := os.Stat(filepath.Join(d, \"WORKSPACE\")); err == nil {\n\t\t\t\treturn filepath.Join(d, outputFile)\n\t\t\t}\n\t\t\td = filepath.Dir(d)\n\t\t}\n\t}\n\n\tlog.Fatalf(\"Could not find workspace root. Set BUILD_WORKSPACE_DIRECTORY or run from within the workspace.\")\n\treturn \"\"\n}\n"
  },
  {
    "path": "kubectl/private/versions/versions.bzl",
    "content": "\"\"\"Generated by update_versions.go - do not edit manually.\"\"\"\n\nLATEST_KUBECTL_VERSION = \"1.35.0\"\n\nVERSIONS = {\n    \"1.35.0\": {\n        \"darwin_amd64\": \"2447cb78911b10a667202b078eeb30541ec78d1280c3682921dc81607e148d96\",\n        \"darwin_arm64\": \"cf699c56340dc775230fde4ef84237d27563ea6ef52164c7d078072b586c3918\",\n        \"linux_amd64\": \"a2e984a18a0c063279d692533031c1eff93a262afcc0afdc517375432d060989\",\n        \"linux_arm64\": \"58f82f9fe796c375c5c4b8439850b0f3f4d401a52434052f2df46035a8789e25\",\n    },\n    \"1.34.3\": {\n        \"darwin_amd64\": \"657afbd0e653c4ce3af1b5a645a4eaba282cf8eb2bcda7191ff60866e50e4d7f\",\n        \"darwin_arm64\": \"e51367d2107d605f4edd7c2fb25897b0c0695a7de1a9f9d04cd6c9356b890b14\",\n        \"linux_amd64\": \"ab60ca5f0fd60c1eb81b52909e67060e3ba0bd27e55a8ac147cbc2172ff14212\",\n        \"linux_arm64\": \"46913a7aa0327f6cc2e1cc2775d53c4a2af5e52f7fd8dacbfbfd098e757f19e9\",\n    },\n    \"1.33.7\": {\n        \"darwin_amd64\": \"45be3f5293da84d97e86580a541b247fe3cec60196fdd6abd2b811d7dd4d3f1b\",\n        \"darwin_arm64\": \"2e333f56d115081af83a48b5f31a91fb32852550f8117a0a31cf8bae2e601704\",\n        \"linux_amd64\": \"471d94e208a89be62eb776700fc8206cbef11116a8de2dc06fc0086b0015375b\",\n        \"linux_arm64\": \"fa7ee98fdb6fba92ae05b5e0cde0abd5972b2d9a4a084f7052a1fd0dce6bc1de\",\n    },\n    \"1.32.11\": {\n        \"darwin_amd64\": \"8d0b610df71632d0e9b9c1aa16dde5ec666c05bf24e401ecf20fd27af16879ad\",\n        \"darwin_arm64\": \"a39978a062f0df17d4a5551bd2e3a91eda90039196653935c50140be547141d3\",\n        \"linux_amd64\": \"48581d0e808bd8b7d3c3fc014e86b170e25a987df04c8a879b982b28a5180815\",\n        \"linux_arm64\": \"b1c91c106ec20e61c5dff869e9a39e6af4fb96572bddaac9cce307dfa3ed2348\",\n    },\n    \"1.31.14\": {\n        \"darwin_amd64\": \"efcaa4da4ff8d1a4d08e972f24c06ab99de2f16bd91c436fd602a275b73d3b78\",\n        \"darwin_arm64\": \"ee72e65cc206edc9b0d878e6d896fa31683dc1ba4c04944984efa07eebfc4725\",\n        \"linux_amd64\": \"8791ec7c8966b61420d55103a5fb948de9f0ca3d7306d789734975ad9704bdb0\",\n        \"linux_arm64\": \"3abb0c2d7121e1833831f56fd857a93de386e76d14b64baf86220d0afe495209\",\n    },\n    \"1.30.14\": {\n        \"darwin_amd64\": \"9d9e5062fdba88b45f57d8fe322545d129cc5ef1e572f7f4f0c4b6579162b7da\",\n        \"darwin_arm64\": \"af47a6a5f25630aa2bd68e56d21110b46aa1595a2d99f936637c75bc6bb65016\",\n        \"linux_amd64\": \"7ccac981ece0098284d8961973295f5124d78eab7b89ba5023f35591baa16271\",\n        \"linux_arm64\": \"a32e46ae15fe41292dc6a7cd76beba7104282a5a3fa9e3686319000a537f4f5d\",\n    },\n    \"1.29.15\": {\n        \"darwin_amd64\": \"fe172c614d423b31d57dca699f1c3990042bfb90768512814d2614dce4563f15\",\n        \"darwin_arm64\": \"1505a09992579bdc94565d872d840c97acfa84cc0478302491b58a754d4de5d6\",\n        \"linux_amd64\": \"3473e14c7b024a6e5403c6401b273b3faff8e5b1fed022d633815eb3168e4516\",\n        \"linux_arm64\": \"a41984dc0ff34ee05f1283ebd9b3121c003b3469b97214738246faa5b6788f7c\",\n    },\n    \"1.28.15\": {\n        \"darwin_amd64\": \"3180c84131002037d60fe7322794c20297d0e1b1514eaea20e33f77a00d8f2f4\",\n        \"darwin_arm64\": \"06a276bdb6da95af148d589f6c983ec8ea10c38f277ced6d97123938c8146078\",\n        \"linux_amd64\": \"1f7651ad0b50ef4561aa82e77f3ad06599b5e6b0b2a5fb6c4f474d95a77e41c5\",\n        \"linux_arm64\": \"7d45d9620e67095be41403ed80765fe47fcfbf4b4ed0bf0d1c8fe80345bda7d3\",\n    },\n    \"1.27.16\": {\n        \"darwin_amd64\": \"8d7f339660ba9b33ed56d540bed41b37babc945975a9e7027010697249b9ac5a\",\n        \"darwin_arm64\": \"d6bc47098bcb13a0ff5c267b30021b499aff4d960bd92610c2b0bc6f6e7246c9\",\n        \"linux_amd64\": \"97ea7cd771d0c6e3332614668a40d2c5996f0053ff11b44b198ea84dba0818cb\",\n        \"linux_arm64\": \"2f50cb29d73f696ffb57437d3e2c95b22c54f019de1dba19e2b834e0b4501eb9\",\n    },\n    \"1.26.15\": {\n        \"darwin_amd64\": \"ad4e980f9c304840ec9227a78a998e132ea23f3ca1bc0df7718ed160341bad0b\",\n        \"darwin_arm64\": \"c20b920d7e8e3ce3209c7c109fcfc4c09ad599613bc04b72c3f70d9fee598b68\",\n        \"linux_amd64\": \"b75f359e6fad3cdbf05a0ee9d5872c43383683bb8527a9e078bb5b8a44350a41\",\n        \"linux_arm64\": \"1396313f0f8e84ab1879757797992f1af043e1050283532e0fd8469902632216\",\n    },\n    \"1.25.16\": {\n        \"darwin_amd64\": \"34e87fdf0613502edbd2a2b00de5ee8c7789ab10e33257d14423dc6879321920\",\n        \"darwin_arm64\": \"d364f73df218b02642d06f3fa9b7345d64c03567b96ca21d361b487f48a33ccc\",\n        \"linux_amd64\": \"5a9bc1d3ebfc7f6f812042d5f97b82730f2bdda47634b67bddf36ed23819ab17\",\n        \"linux_arm64\": \"d6c23c80828092f028476743638a091f2f5e8141273d5228bf06c6671ef46924\",\n    },\n    \"1.24.17\": {\n        \"darwin_amd64\": \"1eb904b2c1148ff8431b0bd86677287a48bff000f93fd2d36377fbe956bd1e49\",\n        \"darwin_arm64\": \"7addbe3f1e22a366fa05aed4f268e77e83d902b40a5854e192b4205ed92e5f8d\",\n        \"linux_amd64\": \"3e9588e3326c7110a163103fc3ea101bb0e85f4d6fd228cf928fa9a2a20594d5\",\n        \"linux_arm64\": \"66885bda3a202546778c77f0b66dcf7f576b5a49ff9456acf61329da784a602d\",\n    },\n    \"1.23.17\": {\n        \"darwin_amd64\": \"7ece6543e3ca2ae9698ef61bbb2a4e249aa21319df4ea1b27c136a9b005dd7d8\",\n        \"darwin_arm64\": \"3b4590d67b31e3a94a9633064571c981907555da5376c34960cddfcd552f6114\",\n        \"linux_amd64\": \"f09f7338b5a677f17a9443796c648d2b80feaec9d6a094ab79a77c8a01fde941\",\n        \"linux_arm64\": \"c4a48fdc6038beacbc5de3e4cf6c23639b643e76656aabe2b7798d3898ec7f05\",\n    },\n    \"1.22.17\": {\n        \"darwin_amd64\": \"c3b8ae5ad48e1e126b5db2e7e22bb1e6ac54901a7f94ce499d12316f705e5e15\",\n        \"darwin_arm64\": \"b2d881bd6d3c688645cbc9e5b4cf4fe8945e1cfc3f2c07c795d2ee605ce4e568\",\n        \"linux_amd64\": \"7506a0ae7a59b35089853e1da2b0b9ac0258c5309ea3d165c3412904a9051d48\",\n        \"linux_arm64\": \"8fc2f8d5c80a6bf60be06f8cf28679a05ce565ce0bc81e70aaac38e0f7da6259\",\n    },\n    \"1.21.14\": {\n        \"darwin_amd64\": \"30c529fe2891eb93dda99597b5c84cb10d2318bb92ae89e1e6189b3ae5fb6296\",\n        \"darwin_arm64\": \"e0e6e413e19abc9deb15f9bd3c72f73ff5539973758e64ebca0f5eb085de6a00\",\n        \"linux_amd64\": \"0c1682493c2abd7bc5fe4ddcdb0b6e5d417aa7e067994ffeca964163a988c6ee\",\n        \"linux_arm64\": \"a23151bca5d918e9238546e7af416422b51cda597a22abaae5ca50369abfbbaa\",\n    },\n    \"1.20.15\": {\n        \"darwin_amd64\": \"6b6cf555a34271379b45013dfa9b580329314254aafc91b543bf2d83ebd1db74\",\n        \"linux_amd64\": \"d283552d3ef3b0fd47c08953414e1e73897a1b3f88c8a520bb2e7de4e37e96f3\",\n        \"linux_arm64\": \"d479febfb2e967bd86240b5c0b841e40e39e1ef610afd6f224281a23318c13dc\",\n    },\n}\n"
  },
  {
    "path": "kustomize/BUILD.bazel",
    "content": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\nload(\"//kustomize/private:resolved_toolchain.bzl\", \"resolved_toolchain\")\n\ntoolchain_type(\n    name = \"toolchain_type\",\n    visibility = [\"//visibility:public\"],\n)\n\nresolved_toolchain(\n    name = \"resolved_toolchain\",\n    visibility = [\"//visibility:public\"],\n)\n\nbzl_library(\n    name = \"defs\",\n    srcs = [\"defs.bzl\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//kustomize/private:extension\",\n        \"//kustomize/private:kustomization\",\n        \"//kustomize/private:kustomize_binary\",\n        \"//kustomize/private:show\",\n        \"//kustomize/private:toolchain\",\n    ],\n)\n"
  },
  {
    "path": "kustomize/defs.bzl",
    "content": "\"\"\"Public API for kustomize rules.\"\"\"\n\nload(\"//kustomize/private:extension.bzl\", _kustomize = \"kustomize\")\nload(\"//kustomize/private:kustomization.bzl\", _kustomization = \"kustomization\")\nload(\"//kustomize/private:kustomize_binary.bzl\", _kustomize_binary = \"kustomize_binary\")\nload(\"//kustomize/private:providers.bzl\", _KustomizeInfo = \"KustomizeInfo\")\nload(\"//kustomize/private:show.bzl\", _show = \"show\")\nload(\"//kustomize/private:toolchain.bzl\", _kustomize_toolchain = \"kustomize_toolchain\")\n\nKustomizeInfo = _KustomizeInfo\nkustomize = _kustomize\nkustomization = _kustomization\nkustomize_binary = _kustomize_binary\nkustomize_toolchain = _kustomize_toolchain\nshow = _show\n"
  },
  {
    "path": "kustomize/private/BUILD.bazel",
    "content": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\n\npackage(\n    default_visibility = [\"//kustomize:__subpackages__\"],\n)\n\nbzl_library(\n    name = \"extension\",\n    srcs = [\"extension.bzl\"],\n    deps = [\n        \":platforms\",\n        \"//kustomize/private/versions\",\n        \"@bazel_tools//tools/build_defs/repo:cache.bzl\",\n        \"@bazel_tools//tools/build_defs/repo:http.bzl\",\n        \"@bazel_tools//tools/build_defs/repo:utils.bzl\",\n    ],\n)\n\nbzl_library(\n    name = \"kustomization\",\n    srcs = [\"kustomization.bzl\"],\n    deps = [\n        \":providers\",\n        \"//adapters:providers\",\n        \"//stamper:stamp\",\n    ],\n)\n\nbzl_library(\n    name = \"toolchain\",\n    srcs = [\"toolchain.bzl\"],\n    deps = [\":providers\"],\n)\n\nbzl_library(\n    name = \"kustomize_binary\",\n    srcs = [\"kustomize_binary.bzl\"],\n)\n\nbzl_library(\n    name = \"platforms\",\n    srcs = [\"platforms.bzl\"],\n)\n\nbzl_library(\n    name = \"providers\",\n    srcs = [\"providers.bzl\"],\n)\n\nbzl_library(\n    name = \"resolved_toolchain\",\n    srcs = [\"resolved_toolchain.bzl\"],\n)\n\nbzl_library(\n    name = \"show\",\n    srcs = [\"show.bzl\"],\n)\n"
  },
  {
    "path": "kustomize/private/extension.bzl",
    "content": "\"\"\"Module extension for kustomize toolchain.\"\"\"\n\nload(\"@bazel_tools//tools/build_defs/repo:http.bzl\", \"http_archive\")\nload(\"//kustomize/private:platforms.bzl\", \"PLATFORMS\")\nload(\"//kustomize/private/versions:versions.bzl\", \"LATEST_KUSTOMIZE_VERSION\", \"VERSIONS\")\n\ndef _kustomize_hub_impl(rctx):\n    kustomize_hub_build_content = \"\"\"\npackage(\n    default_visibility = [\"//visibility:public\"]\n)\n\"\"\"\n    toolchains_build_content = \"\"\n\n    for [platform, meta] in PLATFORMS.items():\n        toolchains_build_content += \"\"\"\ntoolchain(\n    name = \"{platform}_toolchain\",\n    exec_compatible_with = {compatible_with},\n    toolchain = \"@kustomize_{platform}//:kustomize_toolchain\",\n    toolchain_type = \"@rules_gitops//kustomize:toolchain_type\",\n)\n\"\"\".format(\n            platform = platform,\n            compatible_with = meta.compatible_with,\n        )\n\n    rctx.file(\"toolchains/BUILD.bazel\", content = toolchains_build_content)\n    rctx.file(\"BUILD.bazel\", content = kustomize_hub_build_content)\n\n_kustomize_hub = repository_rule(\n    implementation = _kustomize_hub_impl,\n    attrs = {},\n)\n\nKUSTOMIZE_TOOLCHAIN_BUILD = \"\"\"load(\"@rules_gitops//kustomize:defs.bzl\", \"kustomize_toolchain\")\nkustomize_toolchain(\n    name = \"kustomize_toolchain\",\n    executable = \"kustomize\"\n)\n\"\"\"\n\ndef _kustomize_extension_impl(module_ctx):\n    kustomize_version = LATEST_KUSTOMIZE_VERSION\n\n    for mod in module_ctx.modules:\n        if len(mod.tags.toolchain) > 1:\n            fail(\"Expected kustomize toolchain to only be declared once\")\n\n        # Allow the root module to override the kustomize toolchain version\n        if mod.is_root and len(mod.tags.toolchain) == 1:\n            kustomize_version = mod.tags.toolchain[0].version\n\n    if not VERSIONS[kustomize_version]:\n        fail(\"No matching version found for kustomize v{}\".format(kustomize_version))\n\n    binaries = VERSIONS[kustomize_version]\n\n    for [platform, sha256] in binaries.items():\n        http_archive(\n            name = \"kustomize_{}\".format(platform),\n            urls = [\n                \"https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/v{version}/kustomize_v{version}_{platform}.tar.gz\".format(version = kustomize_version, platform = platform.replace(\"-\", \"_\")),\n            ],\n            build_file_content = KUSTOMIZE_TOOLCHAIN_BUILD,\n            sha256 = sha256,\n        )\n\n    _kustomize_hub(\n        name = \"kustomize\",\n    )\n\n    return module_ctx.extension_metadata()\n\nkustomize = module_extension(\n    implementation = _kustomize_extension_impl,\n    tag_classes = {\n        \"toolchain\": tag_class(\n            attrs = {\n                \"version\": attr.string(\n                    doc = \"kustomize binary version\",\n                ),\n            },\n        ),\n    },\n)\n"
  },
  {
    "path": "kustomize/private/kustomization.bzl",
    "content": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License. You may obtain a copy\n# of the License at http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software distributed under\n# the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n# OF ANY KIND, either express or implied. See the License for the specific language\n# governing permissions and limitations under the License.\n\n\"\"\"Rule for building kustomization overlays.\"\"\"\n\nload(\"//adapters:providers.bzl\", \"K8sPushInfo\")\nload(\"//kustomize/private:providers.bzl\", \"KustomizeInfo\")\nload(\"//stamper:stamp.bzl\", \"stamp\")\n\ndef _stamp_file(ctx, infile, output):\n    stamps = [ctx.file._info_file]\n    stamp_args = [\n        \"--stamp-info-file=%s\" % sf.path\n        for sf in stamps\n    ]\n    ctx.actions.run(\n        executable = ctx.executable._stamper,\n        arguments = [\n            \"--format-file=%s\" % infile.path,\n            \"--output=%s\" % output.path,\n        ] + stamp_args,\n        inputs = [infile] + stamps,\n        outputs = [output],\n        mnemonic = \"Stamp\",\n        tools = [ctx.executable._stamper],\n    )\n\ndef _is_ignored_src(src):\n    basename = src.rsplit(\"/\", 1)[-1]\n    return basename.startswith(\".\")\n\nVERIFY_IMAGE_SCRIPT = \"\"\"\nif grep -E 'image:\\\\s+@{{0,2}}//' {out}; then\n    echo \"ERROR: Found unreplaced bazel label in kustomize output\" >&2\n    exit 1\nfi\n\"\"\"\n\n_script_template = \"\"\"\\\n#!/usr/bin/env bash\nset -euo pipefail\n{kustomize} build --load-restrictor LoadRestrictionsNone {kustomize_dir} {template_part} {resolver_part} >{out}\n{verify}\n\"\"\"\n\ndef _kustomization_impl(ctx):\n    kustomization_yaml_file = ctx.actions.declare_file(ctx.attr.name + \"/kustomization.yaml\")\n    root = kustomization_yaml_file.dirname\n\n    upupup = \"/\".join([\"..\"] * (root.count(\"/\") + 1))\n    use_stamp = False\n    tmpfiles = []\n    kustomization_yaml = \"apiVersion: kustomize.config.k8s.io/v1beta1\\nkind: Kustomization\\n\"\n    kustomization_yaml += \"sortOptions:\\n  order: legacy\\n\"\n    kustomization_yaml += \"resources:\\n\"\n    for _, f in enumerate(ctx.files.manifests):\n        kustomization_yaml += \"- {}/{}\\n\".format(upupup, f.path)\n\n    if ctx.attr.namespace:\n        kustomization_yaml += \"namespace: '{}'\\n\".format(ctx.attr.namespace)\n        use_stamp = use_stamp or \"{\" in ctx.attr.namespace\n\n    if ctx.attr.name_prefix:\n        kustomization_yaml += \"namePrefix: '{}'\\n\".format(ctx.attr.name_prefix)\n        use_stamp = use_stamp or \"{\" in ctx.attr.name_prefix\n\n    if ctx.attr.name_suffix:\n        kustomization_yaml += \"nameSuffix: '{}'\\n\".format(ctx.attr.name_suffix)\n        use_stamp = use_stamp or \"{\" in ctx.attr.name_suffix\n\n    if ctx.attr.configurations:\n        kustomization_yaml += \"configurations:\\n\"\n        for _, f in enumerate(ctx.files.configurations):\n            kustomization_yaml += \"- {}/{}\\n\".format(upupup, f.path)\n\n    if ctx.files.patches:\n        kustomization_yaml += \"patches:\\n\"\n        for _, f in enumerate(ctx.files.patches):\n            # TODO: this changed in later versions of kustomize\n            kustomization_yaml += \"- path: {}/{}\\n\".format(upupup, f.path)\n\n    if ctx.attr.image_name_patches or ctx.attr.image_tag_patches:\n        kustomization_yaml += \"images:\\n\"\n        for image, new_tag in ctx.attr.image_tag_patches.items():\n            new_name = ctx.attr.image_name_patches.get(image, default = None)\n            kustomization_yaml += \"- name: \\\"{}\\\"\\n\".format(image)\n            kustomization_yaml += \"  newTag: \\\"{}\\\"\\n\".format(new_tag)\n            if new_name != None:\n                kustomization_yaml += \"  newName: \\\"{}\\\"\\n\".format(new_name)\n        for image, new_name in ctx.attr.image_name_patches.items():\n            if ctx.attr.image_tag_patches.get(image, default = None) == None:\n                kustomization_yaml += \"- name: \\\"{}\\\"\\n\".format(image)\n                kustomization_yaml += \"  newName: \\\"{}\\\"\\n\".format(new_name)\n\n    if ctx.attr.common_labels:\n        kustomization_yaml += \"labels:\\n- includeSelectors: true\\n  pairs:\\n\"\n        for k in ctx.attr.common_labels:\n            use_stamp = use_stamp or \"{\" in ctx.attr.common_labels[k]\n            kustomization_yaml += \"    {}: '{}'\\n\".format(k, ctx.attr.common_labels[k])\n\n    if ctx.attr.common_annotations:\n        kustomization_yaml += \"commonAnnotations:\\n\"\n        for k in ctx.attr.common_annotations:\n            use_stamp = use_stamp or \"{\" in ctx.attr.common_annotations[k]\n            kustomization_yaml += \"  {}: '{}'\\n\".format(k, ctx.attr.common_annotations[k])\n\n    kustomization_yaml += \"generatorOptions:\\n\"\n\n    kustomization_yaml += \"  disableNameSuffixHash: {}\\n\".format(str(ctx.attr.disable_name_suffix_hash).lower())\n\n    if ctx.attr.configmaps_srcs:\n        maps = dict()  # configmap name to list of File objects\n        for target in ctx.attr.configmaps_srcs:\n            for src in target.files.to_list():\n                # ignore dot files\n                if _is_ignored_src(src.path):\n                    continue\n                mapname = src.path.rsplit(\"/\")[-2]\n                if not mapname in maps:\n                    maps[mapname] = []\n                maps[mapname].append(src)\n        kustomization_yaml += \"configMapGenerator:\\n\"\n        for cmname in maps:\n            kustomization_yaml += \"- name: {}\\n\".format(cmname)\n            kustomization_yaml += \"  files:\\n\"\n            for f in maps[cmname]:\n                kustomization_yaml += \"  - {}/{}\\n\".format(upupup, f.path)\n\n    if ctx.attr.secrets_srcs:\n        maps = dict()  # secret name to list of File objects\n        for target in ctx.attr.secrets_srcs:\n            for src in target.files.to_list():\n                # ignore dot files\n                if _is_ignored_src(src.path):\n                    continue\n                mapname = src.path.rsplit(\"/\")[-2]\n                if not mapname in maps:\n                    maps[mapname] = []\n                maps[mapname].append(src)\n        kustomization_yaml += \"secretGenerator:\\n\"\n        for cmname in maps:\n            kustomization_yaml += \"- name: {}\\n\".format(cmname)\n            kustomization_yaml += \"  type: Opaque\\n\"\n            kustomization_yaml += \"  files:\\n\"\n            for f in maps[cmname]:\n                kustomization_yaml += \"  - {}/{}\\n\".format(upupup, f.path)\n\n    if use_stamp:\n        kustomization_yaml_unstamped_file = ctx.actions.declare_file(ctx.attr.name + \"/unstamped.yaml\")\n        ctx.actions.write(kustomization_yaml_unstamped_file, kustomization_yaml)\n        _stamp_file(ctx, kustomization_yaml_unstamped_file, kustomization_yaml_file)\n    else:\n        ctx.actions.write(kustomization_yaml_file, kustomization_yaml)\n\n    transitive_runfiles = []\n    resolver_part = \"\"\n    if ctx.attr.images:\n        resolver_part += \" | {resolver} \".format(resolver = ctx.executable._resolver.path)\n        tmpfiles.append(ctx.executable._resolver)\n        for img in ctx.attr.images:\n            kpi = img[K8sPushInfo]\n            regrepo = kpi.registry + \"/\" + kpi.repository\n            if \"{\" in regrepo:\n                regrepo = stamp(ctx, regrepo, tmpfiles, ctx.attr.name + regrepo.replace(\"/\", \"_\"))\n\n            resolver_part += \" --image {}={}@$(cat {})\".format(str(kpi.image_label).replace(\"@@//\", \"//\"), regrepo, kpi.digestfile.path)\n\n            tmpfiles.append(kpi.digestfile)\n            transitive_runfiles.append(img[DefaultInfo].default_runfiles)\n\n    template_part = \"\"\n    if ctx.attr.substitutions or ctx.attr.deps:\n        template_part += \"| {} --stamp_info_file={} \".format(ctx.executable._template_engine.path, ctx.file._info_file.path)\n        tmpfiles.append(ctx.executable._template_engine)\n        tmpfiles.append(ctx.file._info_file)\n        for k in ctx.attr.substitutions:\n            template_part += \"--variable=%s=%s \" % (k, ctx.attr.substitutions[k])\n        if ctx.attr.start_tag:\n            template_part += \"--start_tag=%s \" % ctx.attr.start_tag\n        if ctx.attr.end_tag:\n            template_part += \"--end_tag=%s \" % ctx.attr.end_tag\n        d = {\n            str(ctx.attr.deps[i].label): ctx.files.deps[i].path\n            for i in range(0, len(ctx.attr.deps))\n        }\n        template_part += \" \".join([\"--imports=%s=%s\" % (k, d[k]) for k in d])\n        template_part += \" \"\n        template_part += \" \".join([\n            \"--imports=%s=%s\" % (k, d[str(ctx.label.relative(ctx.attr.deps_aliases[k]))])\n            for k in ctx.attr.deps_aliases\n        ])\n\n        # Image name substitutions\n        if ctx.attr.images:\n            for img in ctx.attr.images:\n                kpi = img[K8sPushInfo]\n                regrepo = kpi.registry + \"/\" + kpi.repository\n                if \"{\" in regrepo:\n                    regrepo = stamp(ctx, regrepo, tmpfiles, ctx.attr.name + regrepo.replace(\"/\", \"_\"))\n                template_part += \" --variable={}={}@$(cat {})\".format(str(kpi.image_label).replace(\"@@//\", \"//\"), regrepo, kpi.digestfile.path)\n\n                # Image digest\n                template_part += \" --variable={}=$(cat {} | cut -d ':' -f 2)\".format(str(kpi.image_label) + \".digest\", kpi.digestfile.path)\n                template_part += \" --variable={}=$(cat {} | cut -c 8-17)\".format(str(kpi.image_label) + \".short-digest\", kpi.digestfile.path)\n                if str(kpi.image_label).startswith(\"@//\"):\n                    # Bazel 6 add a @ prefix to the image label\n                    label = str(kpi.image_label)[1:]\n                    template_part += \" --variable={}=$(cat {} | cut -d ':' -f 2)\".format(str(label) + \".digest\", kpi.digestfile.path)\n                    template_part += \" --variable={}=$(cat {} | cut -c 8-17)\".format(str(label) + \".short-digest\", kpi.digestfile.path)\n\n                if hasattr(kpi, \"legacy_image_name\"):\n                    template_part += \" --variable={}={}@$(cat {})\".format(kpi.legacy_image_name, regrepo, kpi.digestfile.path)\n\n        template_part += \" \"\n\n    kustomize_executable = ctx.toolchains[\"@rules_gitops//kustomize:toolchain_type\"].kustomizeinfo.executable\n\n    script = ctx.actions.declare_file(\"%s-kustomize\" % ctx.label.name)\n    script_content = _script_template.format(\n        kustomize = kustomize_executable.path,\n        kustomize_dir = root,\n        resolver_part = resolver_part,\n        template_part = template_part,\n        out = ctx.outputs.yaml.path,\n        verify = VERIFY_IMAGE_SCRIPT.format(\n            out = ctx.outputs.yaml.path,\n        ) if ctx.attr.verify_images else \"\",\n    )\n    ctx.actions.write(script, script_content, is_executable = True)\n\n    ctx.actions.run(\n        outputs = [ctx.outputs.yaml],\n        inputs = ctx.files.manifests + ctx.files.configmaps_srcs + ctx.files.secrets_srcs + ctx.files.configurations + [kustomization_yaml_file] + tmpfiles + ctx.files.patches + ctx.files.deps,\n        executable = script,\n        mnemonic = \"Kustomize\",\n        tools = [kustomize_executable],\n    )\n\n    runfiles = ctx.runfiles(files = ctx.files.deps).merge_all(transitive_runfiles)\n\n    transitive_files = [m[DefaultInfo].files for m in ctx.attr.manifests if KustomizeInfo in m]\n    transitive_files += [obj[DefaultInfo].files for obj in ctx.attr.objects]\n\n    transitive_image_pushes = [m[KustomizeInfo].image_pushes for m in ctx.attr.manifests if KustomizeInfo in m]\n    transitive_image_pushes += [obj[KustomizeInfo].image_pushes for obj in ctx.attr.objects]\n\n    return [\n        DefaultInfo(\n            files = depset(\n                [ctx.outputs.yaml],\n                transitive = transitive_files,\n            ),\n            runfiles = runfiles,\n        ),\n        KustomizeInfo(\n            image_pushes = depset(\n                ctx.attr.images,\n                transitive = transitive_image_pushes,\n            ),\n        ),\n    ]\n\nkustomization = rule(\n    implementation = _kustomization_impl,\n    attrs = {\n        \"configmaps_srcs\": attr.label_list(allow_files = True),\n        \"secrets_srcs\": attr.label_list(allow_files = True),\n        \"deps_aliases\": attr.string_dict(default = {}),\n        \"disable_name_suffix_hash\": attr.bool(default = True),\n        \"end_tag\": attr.string(default = \"}}\"),\n        \"images\": attr.label_list(doc = \"a list of image pushes used in manifests\", providers = [K8sPushInfo]),\n        \"manifests\": attr.label_list(allow_files = True),\n        \"name_prefix\": attr.string(),\n        \"name_suffix\": attr.string(),\n        \"namespace\": attr.string(),\n        \"objects\": attr.label_list(doc = \"a list of dependent kustomize objects\", providers = [KustomizeInfo]),\n        \"patches\": attr.label_list(allow_files = True),\n        \"image_name_patches\": attr.string_dict(default = {}, doc = \"set new names for selected images\"),\n        \"image_tag_patches\": attr.string_dict(default = {}, doc = \"set new tags for selected images\"),\n        \"start_tag\": attr.string(default = \"{{\"),\n        \"substitutions\": attr.string_dict(default = {}),\n        \"deps\": attr.label_list(default = [], allow_files = True),\n        \"configurations\": attr.label_list(allow_files = True),\n        \"common_labels\": attr.string_dict(default = {}),\n        \"common_annotations\": attr.string_dict(default = {}),\n        \"verify_images\": attr.bool(doc = \"check whether all images which point to bazel labels were resolved\", default = True),\n        \"_build_user_value\": attr.label(\n            default = Label(\"//stamper:build_user_value.txt\"),\n            allow_single_file = True,\n        ),\n        \"_info_file\": attr.label(\n            default = Label(\"//stamper:more_stable_status.txt\"),\n            allow_single_file = True,\n        ),\n        \"_resolver\": attr.label(\n            default = Label(\"//resolver:resolver\"),\n            cfg = \"exec\",\n            executable = True,\n        ),\n        \"_stamper\": attr.label(\n            default = Label(\"//stamper:stamper\"),\n            cfg = \"exec\",\n            executable = True,\n            allow_files = True,\n        ),\n        \"_template_engine\": attr.label(\n            default = Label(\"//templating:fast_template_engine\"),\n            executable = True,\n            cfg = \"exec\",\n        ),\n    },\n    outputs = {\n        \"yaml\": \"%{name}.yaml\",\n    },\n    toolchains = [\"@rules_gitops//kustomize:toolchain_type\"],\n)\n"
  },
  {
    "path": "kustomize/private/kustomize_binary.bzl",
    "content": "\"\"\"\nSimple rule for running kustomize from the toolchain config\n\"\"\"\n\ndef _kustomize_binary(ctx):\n    executable = ctx.toolchains[\"@rules_gitops//kustomize:toolchain_type\"].kustomizeinfo.executable\n\n    kustomize = ctx.actions.declare_file(ctx.label.name)\n    ctx.actions.symlink(\n        output = kustomize,\n        target_file = executable,\n        is_executable = True,\n    )\n\n    return [\n        DefaultInfo(\n            runfiles = ctx.runfiles(files = [executable, kustomize]),\n            executable = kustomize,\n        ),\n    ]\n\nkustomize_binary = rule(\n    implementation = _kustomize_binary,\n    attrs = {},\n    toolchains = [\"@rules_gitops//kustomize:toolchain_type\"],\n    executable = True,\n)\n"
  },
  {
    "path": "kustomize/private/platforms.bzl",
    "content": "\"\"\"Platform definitions for kustomize toolchain.\"\"\"\n\nPLATFORMS = {\n    \"darwin_amd64\": struct(\n        compatible_with = [\n            \"@platforms//os:macos\",\n            \"@platforms//cpu:x86_64\",\n        ],\n    ),\n    \"darwin_arm64\": struct(\n        compatible_with = [\n            \"@platforms//os:macos\",\n            \"@platforms//cpu:arm64\",\n        ],\n    ),\n    \"linux_amd64\": struct(\n        compatible_with = [\n            \"@platforms//os:linux\",\n            \"@platforms//cpu:x86_64\",\n        ],\n    ),\n    \"linux_arm64\": struct(\n        compatible_with = [\n            \"@platforms//os:linux\",\n            \"@platforms//cpu:x86_64\",\n        ],\n    ),\n}\n"
  },
  {
    "path": "kustomize/private/providers.bzl",
    "content": "\"\"\"Provider definitions for kustomize rules.\"\"\"\n\nKustomizeToolchainInfo = provider(\n    doc = \"Toolchain information about the kustomize executable\",\n    fields = {\n        \"target_tool_path\": \"Path to the kustomize executable for the target platform.\",\n        \"executable\": \"Hermetically download toolchain executable file\",\n    },\n)\n\nKustomizeInfo = provider(\n    doc = \"Information about a kustomization target\",\n    fields = {\n        \"image_pushes\": \"depset of image push executables for images referenced by this kustomization\",\n    },\n)\n"
  },
  {
    "path": "kustomize/private/resolved_toolchain.bzl",
    "content": "\"\"\"This module implements an alias rule to the resolved toolchain.\n\"\"\"\n\nDOC = \"\"\"\\\nExposes a concrete toolchain which is the result of Bazel resolving the\ntoolchain for the execution or target platform.\nWorkaround for https://github.com/bazelbuild/bazel/issues/14009\n\"\"\"\n\n# Forward all the providers\ndef _resolved_toolchain_impl(ctx):\n    toolchain_info = ctx.toolchains[\"//kustomize:toolchain_type\"]\n    return [\n        toolchain_info,\n        toolchain_info.default,\n        toolchain_info.kustomizeinfo,\n        toolchain_info.template_variables,\n    ]\n\n# Copied from java_toolchain_alias\n# https://cs.opensource.google/bazel/bazel/+/master:tools/jdk/java_toolchain_alias.bzl\nresolved_toolchain = rule(\n    implementation = _resolved_toolchain_impl,\n    toolchains = [\"//kustomize:toolchain_type\"],\n    doc = DOC,\n)\n"
  },
  {
    "path": "kustomize/private/show.bzl",
    "content": "\"\"\"Rule for displaying rendered kustomize output.\"\"\"\n\ndef _show_impl(ctx):\n    script_content = \"#!/usr/bin/env bash\\nset -e\\n\"\n\n    kustomize_outputs = []\n    script_template = \"{template_engine} --template={infile} --variable=NAMESPACE={namespace} --stamp_info_file={info_file}\\n\"\n    for dep in ctx.attr.src.files.to_list():\n        kustomize_outputs.append(script_template.format(\n            infile = dep.short_path,\n            template_engine = ctx.executable._template_engine.short_path,\n            namespace = ctx.attr.namespace,\n            info_file = ctx.file._info_file.short_path,\n        ))\n\n    # ensure kustomize outputs are separated by '---' delimiters\n    script_content += \"echo '---'\\n\".join(kustomize_outputs)\n\n    ctx.actions.write(ctx.outputs.executable, script_content, is_executable = True)\n    return [\n        DefaultInfo(runfiles = ctx.runfiles(files = [ctx.executable._template_engine, ctx.file._info_file] + ctx.files.src)),\n    ]\n\nshow = rule(\n    implementation = _show_impl,\n    attrs = {\n        \"src\": attr.label(\n            doc = \"Input file.\",\n            mandatory = True,\n        ),\n        \"namespace\": attr.string(\n            doc = \"kubernetes namespace.\",\n            mandatory = True,\n        ),\n        \"_info_file\": attr.label(\n            default = Label(\"//stamper:more_stable_status.txt\"),\n            allow_single_file = True,\n        ),\n        \"_template_engine\": attr.label(\n            default = Label(\"//templating:fast_template_engine\"),\n            executable = True,\n            cfg = \"exec\",\n        ),\n    },\n    executable = True,\n)\n"
  },
  {
    "path": "kustomize/private/tests/BUILD.bazel",
    "content": "load(\"//kustomize/private:kustomization.bzl\", \"kustomization\")\nload(\"//tools:util.bzl\", \"golden_test\")\n\n# Verify that rule is combining files without processing\nkustomization(\n    name = \"raw\",\n    testonly = True,\n    images = [\n        \"//tests/images:k8s_image\",\n    ],\n    manifests = [\n        \"deployment.yaml\",\n        \"service.yaml\",\n        \"crb.yaml\",\n    ],\n    namespace = \"\",\n    verify_images = False,\n)\n\ngolden_test(\n    name = \"raw_test\",\n    in_file = \"raw\",\n)\n"
  },
  {
    "path": "kustomize/private/tests/crb.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: crb-name\nsubjects:\n- kind: Group\n  name: crb-subject\nroleRef:\n  kind: ClusterRole\n  name: cluster-admin\n"
  },
  {
    "path": "kustomize/private/tests/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp\nspec:\n  selector:\n    matchLabels:\n      app: myapp\n  template:\n    metadata:\n      labels:\n        app: myapp\n    spec:\n      containers:\n      - name: myapp\n        image: //tests/images:nonexistent\n"
  },
  {
    "path": "kustomize/private/tests/goldens/raw.golden",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: crb-name\nroleRef:\n  kind: ClusterRole\n  name: cluster-admin\nsubjects:\n- kind: Group\n  name: crb-subject\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: myapp\nspec:\n  ports:\n  - name: web\n    port: 80\n    targetPort: 8080\n  selector:\n    app: myapp\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp\nspec:\n  selector:\n    matchLabels:\n      app: myapp\n  template:\n    metadata:\n      labels:\n        app: myapp\n    spec:\n      containers:\n      - image: //tests/images:nonexistent\n        name: myapp\n"
  },
  {
    "path": "kustomize/private/tests/service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: myapp\nspec:\n  ports:\n  - port: 80\n    name: web\n    targetPort: 8080\n  selector:\n    app: myapp\n"
  },
  {
    "path": "kustomize/private/toolchain.bzl",
    "content": "\"Kustomize toolchain rule\"\n\nload(\"//kustomize/private:providers.bzl\", \"KustomizeToolchainInfo\")\n\n# Avoid using non-normalized paths (workspace/../other_workspace/path)\ndef _to_manifest_path(ctx, file):\n    if file.short_path.startswith(\"../\"):\n        return \"external/\" + file.short_path[3:]\n    else:\n        return ctx.workspace_name + \"/\" + file.short_path\n\ndef _kustomize_toolchain_impl(ctx):\n    executable = ctx.file.executable\n    target_tool_path = _to_manifest_path(ctx, executable)\n\n    template_variables = platform_common.TemplateVariableInfo({\n        \"KUSTOMIZE_BIN\": target_tool_path,\n    })\n\n    default = DefaultInfo(\n        files = depset([executable]),\n        runfiles = ctx.runfiles(files = [executable]),\n    )\n    kustomizeinfo = KustomizeToolchainInfo(\n        target_tool_path = target_tool_path,\n        executable = executable,\n    )\n    toolchain_info = platform_common.ToolchainInfo(\n        kustomizeinfo = kustomizeinfo,\n        template_variables = template_variables,\n        default = default,\n    )\n    return [\n        default,\n        toolchain_info,\n        template_variables,\n    ]\n\nkustomize_toolchain = rule(\n    implementation = _kustomize_toolchain_impl,\n    attrs = {\n        \"executable\": attr.label(\n            doc = \"A hermetically downloaded executable target for the target platform.\",\n            mandatory = True,\n            allow_single_file = True,\n        ),\n    },\n)\n"
  },
  {
    "path": "kustomize/private/versions/BUILD.bazel",
    "content": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\nload(\"@rules_go//go:def.bzl\", \"go_binary\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"update_versions.go\"],\n    importpath = \"github.com/adobe/rules_gitops/kustomize/private/versions\",\n    visibility = [\"//visibility:private\"],\n)\n\ngo_binary(\n    name = \"update_versions\",\n    embed = [\":go_default_library\"],\n    visibility = [\"//visibility:public\"],\n)\n\nbzl_library(\n    name = \"versions\",\n    srcs = [\"versions.bzl\"],\n    visibility = [\"//kustomize:__subpackages__\"],\n)\n"
  },
  {
    "path": "kustomize/private/versions/update_versions.go",
    "content": "// update_versions downloads kustomize release information from GitHub\n// and generates a versions.bzl file with SHA256 digests for each platform.\n//\n// Usage:\n//\n//\tgo run update_versions.go\n//\n// The script expects BUILD_WORKSPACE_DIRECTORY to be set, or writes to\n// the default location relative to this file's directory.\npackage main\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"sort\"\n\t\"strings\"\n)\n\nconst (\n\treleasesURL = \"https://api.github.com/repos/kubernetes-sigs/kustomize/releases\"\n\toutputFile  = \"kustomize/private/versions/versions.bzl\"\n)\n\n// Asset represents a GitHub release asset\ntype Asset struct {\n\tName              string `json:\"name\"`\n\tDigest            string `json:\"digest\"`\n\tBrowserDownloadURL string `json:\"browser_download_url\"`\n}\n\n// Release represents a GitHub release\ntype Release struct {\n\tTagName     string  `json:\"tag_name\"`\n\tPublishedAt string  `json:\"published_at\"`\n\tAssets      []Asset `json:\"assets\"`\n}\n\n// VersionInfo holds platform -> sha256 mappings for a version\ntype VersionInfo struct {\n\tPlatforms map[string]string\n}\n\nfunc main() {\n\tlog.SetFlags(0)\n\n\t// Fetch releases from GitHub\n\treleases, err := fetchReleases()\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to fetch releases: %v\", err)\n\t}\n\n\t// Parse releases and extract version info\n\tversions, latestVersion := parseReleases(releases)\n\n\t// Generate versions.bzl content\n\tcontent := generateBzl(versions, latestVersion)\n\n\t// Determine output path\n\toutputPath := getOutputPath()\n\n\t// Write the file\n\tif err := os.MkdirAll(filepath.Dir(outputPath), 0755); err != nil {\n\t\tlog.Fatalf(\"Failed to create directory: %v\", err)\n\t}\n\tif err := os.WriteFile(outputPath, []byte(content), 0644); err != nil {\n\t\tlog.Fatalf(\"Failed to write file: %v\", err)\n\t}\n\n\tlog.Printf(\"Successfully wrote %s with %d versions\", outputPath, len(versions))\n}\n\nfunc fetchReleases() ([]Release, error) {\n\t// GitHub API may paginate, so we need to fetch all pages\n\tvar allReleases []Release\n\turl := releasesURL + \"?per_page=100\"\n\n\tfor url != \"\" {\n\t\treq, err := http.NewRequest(\"GET\", url, nil)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\treq.Header.Set(\"Accept\", \"application/vnd.github.v3+json\")\n\t\treq.Header.Set(\"User-Agent\", \"rules_gitops-version-updater\")\n\n\t\tresp, err := http.DefaultClient.Do(req)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tdefer resp.Body.Close()\n\n\t\tif resp.StatusCode != http.StatusOK {\n\t\t\tbody, _ := io.ReadAll(resp.Body)\n\t\t\treturn nil, fmt.Errorf(\"GitHub API returned status %d: %s\", resp.StatusCode, string(body))\n\t\t}\n\n\t\tvar releases []Release\n\t\tif err := json.NewDecoder(resp.Body).Decode(&releases); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to decode JSON: %w\", err)\n\t\t}\n\t\tallReleases = append(allReleases, releases...)\n\n\t\t// Check for pagination\n\t\turl = getNextPageURL(resp.Header.Get(\"Link\"))\n\t}\n\n\treturn allReleases, nil\n}\n\nfunc getNextPageURL(linkHeader string) string {\n\tif linkHeader == \"\" {\n\t\treturn \"\"\n\t}\n\t// Parse Link header: <url>; rel=\"next\", <url>; rel=\"last\"\n\tfor _, part := range strings.Split(linkHeader, \",\") {\n\t\tpart = strings.TrimSpace(part)\n\t\tif strings.Contains(part, `rel=\"next\"`) {\n\t\t\t// Extract URL between < and >\n\t\t\tstart := strings.Index(part, \"<\")\n\t\t\tend := strings.Index(part, \">\")\n\t\t\tif start >= 0 && end > start {\n\t\t\t\treturn part[start+1 : end]\n\t\t\t}\n\t\t}\n\t}\n\treturn \"\"\n}\n\nfunc parseReleases(releases []Release) (map[string]*VersionInfo, string) {\n\tversions := make(map[string]*VersionInfo)\n\n\t// Regex to match kustomize version tags\n\tversionRe := regexp.MustCompile(`^kustomize/v(\\d+\\.\\d+\\.\\d+)$`)\n\t// Regex to extract platform from asset name\n\tassetRe := regexp.MustCompile(`^kustomize_v[\\d.]+_([a-z]+_[a-z0-9]+)\\.tar\\.gz$`)\n\n\t// Track latest version by publish date\n\tlatestVersion := \"\"\n\tlatestPublishedAt := \"\"\n\n\tfor _, release := range releases {\n\t\tmatches := versionRe.FindStringSubmatch(release.TagName)\n\t\tif matches == nil {\n\t\t\tcontinue // Not a kustomize release\n\t\t}\n\t\tversion := matches[1]\n\n\t\tversionInfo := &VersionInfo{\n\t\t\tPlatforms: make(map[string]string),\n\t\t}\n\n\t\tfor _, asset := range release.Assets {\n\t\t\tassetMatches := assetRe.FindStringSubmatch(asset.Name)\n\t\t\tif assetMatches == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tplatform := assetMatches[1]\n\n\t\t\t// Extract SHA256 from digest (format: \"sha256:...\")\n\t\t\tsha256 := strings.TrimPrefix(asset.Digest, \"sha256:\")\n\t\t\tif sha256 == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tversionInfo.Platforms[platform] = sha256\n\t\t}\n\n\t\tif len(versionInfo.Platforms) > 0 {\n\t\t\tversions[version] = versionInfo\n\n\t\t\t// Track latest by publish date (ISO 8601 format is lexicographically sortable)\n\t\t\tif release.PublishedAt > latestPublishedAt {\n\t\t\t\tlatestPublishedAt = release.PublishedAt\n\t\t\t\tlatestVersion = version\n\t\t\t}\n\t\t}\n\t}\n\n\treturn versions, latestVersion\n}\n\nfunc generateBzl(versions map[string]*VersionInfo, latestVersion string) string {\n\tvar sb strings.Builder\n\n\t// Sort versions for consistent output (reverse alphabetical puts newer versions first)\n\tversionList := make([]string, 0, len(versions))\n\tfor v := range versions {\n\t\tversionList = append(versionList, v)\n\t}\n\tsort.Sort(sort.Reverse(sort.StringSlice(versionList)))\n\n\tsb.WriteString(`\"\"\"Generated by update_versions.go - do not edit manually.\"\"\"\n\n`)\n\tsb.WriteString(fmt.Sprintf(\"LATEST_KUSTOMIZE_VERSION = %q\\n\\n\", latestVersion))\n\tsb.WriteString(\"VERSIONS = {\\n\")\n\n\tfor _, version := range versionList {\n\t\tinfo := versions[version]\n\t\tsb.WriteString(fmt.Sprintf(\"    %q: {\\n\", version))\n\n\t\t// Sort platforms for consistent output\n\t\tplatforms := make([]string, 0, len(info.Platforms))\n\t\tfor p := range info.Platforms {\n\t\t\tplatforms = append(platforms, p)\n\t\t}\n\t\tsort.Strings(platforms)\n\n\t\tfor _, platform := range platforms {\n\t\t\tsha256 := info.Platforms[platform]\n\t\t\tsb.WriteString(fmt.Sprintf(\"        %q: %q,\\n\", platform, sha256))\n\t\t}\n\t\tsb.WriteString(\"    },\\n\")\n\t}\n\n\tsb.WriteString(\"}\\n\")\n\n\treturn sb.String()\n}\n\nfunc getOutputPath() string {\n\t// Check for BUILD_WORKSPACE_DIRECTORY environment variable\n\tif wsDir := os.Getenv(\"BUILD_WORKSPACE_DIRECTORY\"); wsDir != \"\" {\n\t\treturn filepath.Join(wsDir, outputFile)\n\t}\n\n\t// Fallback: find workspace root by looking for MODULE.bazel or WORKSPACE\n\t// Start from executable directory and walk up\n\texePath, err := os.Executable()\n\tif err != nil {\n\t\tlog.Fatalf(\"Failed to get executable path: %v\", err)\n\t}\n\tdir := filepath.Dir(exePath)\n\n\t// Also try current working directory\n\tcwd, _ := os.Getwd()\n\tfor _, startDir := range []string{dir, cwd} {\n\t\td := startDir\n\t\tfor d != \"/\" && d != \".\" {\n\t\t\tif _, err := os.Stat(filepath.Join(d, \"MODULE.bazel\")); err == nil {\n\t\t\t\treturn filepath.Join(d, outputFile)\n\t\t\t}\n\t\t\tif _, err := os.Stat(filepath.Join(d, \"WORKSPACE\")); err == nil {\n\t\t\t\treturn filepath.Join(d, outputFile)\n\t\t\t}\n\t\t\td = filepath.Dir(d)\n\t\t}\n\t}\n\n\tlog.Fatalf(\"Could not find workspace root. Set BUILD_WORKSPACE_DIRECTORY or run from within the workspace.\")\n\treturn \"\"\n}\n"
  },
  {
    "path": "kustomize/private/versions/versions.bzl",
    "content": "\"\"\"Generated by update_versions.go - do not edit manually.\"\"\"\n\nLATEST_KUSTOMIZE_VERSION = \"5.8.0\"\n\nVERSIONS = {\n    \"5.8.0\": {\n        \"darwin_amd64\": \"2deaa6f96450c0b3204cccd9f159a22278eb6cf85ad545d212d608d2428aeb57\",\n        \"darwin_arm64\": \"d098f62ecda500c752303163838af823f947d245927f3000b629199c1eeeae0f\",\n        \"linux_amd64\": \"4dfa8307358dd9284aa4d2b1d5596766a65b93433e8fa3f9f74498941f01c5ef\",\n        \"linux_arm64\": \"a4f48b4c3d4ca97d748943e19169de85a2e86e80bcc09558603e2aa66fb15ce1\",\n        \"linux_ppc64le\": \"4352a29912b6a15688598ec192e79b841ee8b128ba411592d1de68b585009ec7\",\n        \"linux_s390x\": \"f7be07697c1ba872ff4e99806ac9a86faa7029e108a24ccfa7b47c04c1cc9c0d\",\n    },\n    \"5.7.1\": {\n        \"darwin_amd64\": \"4a0dff80c5644df6bc8f51b342842969004cb6ba5f94dddaabbea7483493273d\",\n        \"darwin_arm64\": \"073e9d16d5a235e2ff83e62d6b76edb5d962adbc33be1e4860c4b3f1f39b33b9\",\n        \"linux_amd64\": \"ea375e7372f9aa029129d4b2d16c66b7750b7f1213c4f66f910d981c895818d8\",\n        \"linux_arm64\": \"4261a040217df3bd6896597c3986d1465925726e4f22a945304b5233a4dcdbda\",\n        \"linux_ppc64le\": \"56b6fbf549080b14ddc738f10a05f78cbc5511cd7ab2014d9eb85f52bb4b7263\",\n        \"linux_s390x\": \"b1eee427af74f3bb53d96e3ba94d5cc7484a35bb1af1495488bc684d60df4488\",\n    },\n    \"5.7.0\": {\n        \"darwin_amd64\": \"277a7401f969ce3945e8f0ff8b0cce6f4353854db1ff89ba070001e3246e7f22\",\n        \"darwin_arm64\": \"c0dac68dc7870e1f673ae4d8fb554df971e0b9b9f0affc4be4c0852f62d0796e\",\n        \"linux_amd64\": \"0d98f06d6d2c2c0ff8923cc136a517af74aaa187f1b9f3e17ff370d0625ede84\",\n        \"linux_arm64\": \"744bb1bc1854b6634dea9eaf6db2f401a734ed25d6837baa6f91157d79c27d5e\",\n        \"linux_ppc64le\": \"752e750d5f349156ea228ae01cf57be22e6cc29f0f05748a1bca7fa870393561\",\n        \"linux_s390x\": \"64898beb154a111c1a98f8cff066fdfa866c4c73505e9a9b5fa6ec39f0292558\",\n    },\n}\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \":dependencyDashboard\",\n    \":enablePreCommit\",\n    \":semanticPrefixFixDepsChoreOthers\",\n    \"group:monorepos\",\n    \"group:recommended\",\n    \"replacements:all\",\n    \"workarounds:all\"\n  ],\n  \"packageRules\": [\n    {\n      \"matchFiles\": [\"MODULE.bazel\"],\n      \"enabled\": false\n    }\n  ]\n}"
  },
  {
    "path": "resolver/BUILD.bazel",
    "content": "load(\"@rules_go//go:def.bzl\", \"go_binary\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"resolver.go\"],\n    importpath = \"github.com/adobe/rules_gitops/resolver\",\n    visibility = [\"//visibility:private\"],\n    deps = [\"//resolver/pkg:go_default_library\"],\n)\n\ngo_binary(\n    name = \"resolver\",\n    embed = [\":go_default_library\"],\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "resolver/pkg/BUILD.bazel",
    "content": "load(\"@rules_go//go:def.bzl\", \"go_library\", \"go_test\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"resolver.go\"],\n    importpath = \"github.com/adobe/rules_gitops/resolver/pkg\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@com_github_ghodss_yaml//:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1/unstructured:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/util/yaml:go_default_library\",\n    ],\n)\n\n# TODO(KZ): this test uses private manifests\ngo_test(\n    name = \"go_default_test\",\n    srcs = [\"resolver_test.go\"],\n    data = glob([\"testdata/**\"]),\n    deps = [\":go_default_library\"],\n)\n"
  },
  {
    "path": "resolver/pkg/resolver.go",
    "content": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\npackage resolver\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n\n\tyamlenc \"github.com/ghodss/yaml\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/util/yaml\"\n)\n\n// ResolveImages reads yaml or json stream from in, deserialize it and replace images with ones specified in imagemap\n// and serialize it back into out stream.\nfunc ResolveImages(in io.Reader, out io.Writer, imgmap map[string]string) error {\n\tpt := imageTagTransformer{images: imgmap}\n\tdecoder := yaml.NewYAMLOrJSONDecoder(in, 1024)\n\tvar err error\n\tfirstObj := true\n\tfor err == nil || isEmptyYamlError(err) {\n\t\tvar obj unstructured.Unstructured\n\t\terr = decoder.Decode(&obj)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif obj.GetName() == \"\" {\n\t\t\treturn fmt.Errorf(\"Missing metadata.name in object %v\", obj)\n\t\t}\n\t\tif obj.GetKind() == \"\" {\n\t\t\treturn fmt.Errorf(\"Missing kind in object %v\", obj)\n\t\t}\n\t\tpt.findAndReplaceTag(obj.Object)\n\t\tbuf, err := yamlenc.Marshal(obj.Object)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Unable to marshal object %v\", obj)\n\t\t}\n\t\tif firstObj {\n\t\t\tfirstObj = false\n\t\t} else {\n\t\t\t_, err = out.Write([]byte(\"---\\n\"))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\t_, err = out.Write(buf)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t}\n\tif err != io.EOF {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc isEmptyYamlError(err error) bool {\n\treturn strings.Contains(err.Error(), \"is missing in 'null'\")\n}\n\ntype imageTagTransformer struct {\n\timages map[string]string\n}\n\n/*\nfindAndReplaceTag replaces the image tags inside one object\nIt searches the object for container session\nthen loops though all images inside containers session, finds matched ones and update the tag name\n*/\nfunc (pt *imageTagTransformer) findAndReplaceTag(obj map[string]interface{}) error {\n\tfound := false\n\n\t// Update [container|spec].image\n\tpaths := []string{\"container\", \"spec\"}\n\tfor _, path := range paths {\n\t\t_, found = obj[path]\n\t\tif found {\n\t\t\terr := pt.updateContainer(obj, path)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\t// Update containers.[image, image]\n\tpaths = []string{\"containers\", \"initContainers\"}\n\tfor _, path := range paths {\n\t\t_, found = obj[path]\n\t\tif found {\n\t\t\terr := pt.updateContainers(obj, path)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t}\n\n\tif !found {\n\t\treturn pt.findContainers(obj)\n\t}\n\treturn nil\n}\n\nfunc (pt *imageTagTransformer) updateContainers(obj map[string]interface{}, path string) error {\n\tif obj[path] == nil {\n\t\treturn nil\n\t}\n\n\tswitch containers := obj[path].(type) {\n\tcase []interface{}:\n\t\tfor i := range containers {\n\t\t\tcontainer, ok := containers[i].(map[string]interface{})\n\t\t\tif !ok {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\timage, found := container[\"image\"]\n\t\t\tif !found {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\timagename, imagenameOk := image.(string)\n\t\t\tif imagenameOk {\n\t\t\t\tif newname, ok := pt.images[imagename]; ok {\n\t\t\t\t\tcontainer[\"image\"] = newname\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif strings.HasPrefix(imagename, \"//\") {\n\t\t\t\t\treturn fmt.Errorf(\"Unresolved image found: %s\", imagename)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\tdefault:\n\t\treturn nil\n\t}\n\n\treturn nil\n}\n\nfunc (pt *imageTagTransformer) updateContainer(obj map[string]interface{}, path string) error {\n\tif obj[path] == nil {\n\t\treturn nil\n\t}\n\tcontainer := obj[path].(map[string]interface{})\n\timage, found := container[\"image\"]\n\tif found {\n\t\timagename, imagenameOk := image.(string)\n\t\tif imagenameOk {\n\t\t\tif strings.HasPrefix(imagename, \"//\") {\n\t\t\t\treturn fmt.Errorf(\"unresolved image found: %s\", imagename)\n\t\t\t}\n\t\t\tif newname, ok := pt.images[imagename]; ok {\n\t\t\t\tcontainer[\"image\"] = newname\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (pt *imageTagTransformer) findContainers(obj map[string]interface{}) error {\n\tfor key := range obj {\n\t\tswitch typedV := obj[key].(type) {\n\t\tcase map[string]interface{}:\n\t\t\terr := pt.findAndReplaceTag(typedV)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\tcase []interface{}:\n\t\t\tfor i := range typedV {\n\t\t\t\titem := typedV[i]\n\t\t\t\ttypedItem, ok := item.(map[string]interface{})\n\t\t\t\tif ok {\n\t\t\t\t\terr := pt.findAndReplaceTag(typedItem)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "resolver/pkg/resolver_test.go",
    "content": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\npackage resolver_test\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\tresolver \"github.com/adobe/rules_gitops/resolver/pkg\"\n)\n\nfunc TestNoError(t *testing.T) {\n\ttestcases := []struct {\n\t\tname   string\n\t\timgmap map[string]string\n\t}{\n\t\t{\"happypath\", map[string]string{\n\t\t\t\"salist\":      \"docker.io/rtb/sacli/cmd/salist/image@sha256:5711bcf54511ab2fef6e08d9c9f9ae3f3a269e66834048465cc7502adb0d489b\",\n\t\t\t\"filewatcher\": \"docker.io/kube/filewatcher/image:tag\",\n\t\t}},\n\t\t{\"cwf\", map[string]string{\n\t\t\t\"helloworld-image\": \"docker.io/kube/hello/image:tag\",\n\t\t}},\n\t\t{\"flinkapp\", map[string]string{\n\t\t\t\"flinkapp-image\": \"docker.io/kube/flink/image:tag\",\n\t\t}},\n\t\t{\"zk\", map[string]string{\n\t\t\t\"zk-image\": \"dummy\",\n\t\t}},\n\t\t{\"emptyinit\", map[string]string{\n\t\t\t\"helloworld-image\": \"docker.io/kube/hello/image:tag\",\n\t\t}},\n\t\t{\"digest\", map[string]string{\n\t\t\t\"helloworld-image\": \"dummy\",\n\t\t}},\n\t}\n\tfor _, testcase := range testcases {\n\t\tt.Run(testcase.name, func(t *testing.T) {\n\t\t\tinfn := fmt.Sprintf(\"testdata/%s.yaml\", testcase.name)\n\t\t\texpectedfn := fmt.Sprintf(\"testdata/%s.expected.yaml\", testcase.name)\n\t\t\tinf, err := os.Open(infn)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Unable to open file %s\", infn)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdefer inf.Close()\n\t\t\texpectedb, err := ioutil.ReadFile(expectedfn)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Unable to read file %s\", expectedfn)\n\t\t\t\treturn\n\t\t\t}\n\t\t\texpected := strings.TrimSpace(string(expectedb))\n\t\t\tvar outbuf bytes.Buffer\n\t\t\terr = resolver.ResolveImages(inf, &outbuf, testcase.imgmap)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Unexpected error %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif strings.TrimSpace(outbuf.String()) != expected {\n\t\t\t\tt.Errorf(\"Unexpected output: %s\", outbuf.String())\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "resolver/pkg/testdata/cwf.expected.yaml",
    "content": "apiVersion: apps/v1\nkind: CronWorkFlow\nmetadata:\n  name: aaa\n  namespace: stats-dev\nspec:\n  workflowSpec:\n    metadata:\n      labels:\n        app: app\n    templates:\n      container:\n        image: docker.io/kube/hello/image:tag\n"
  },
  {
    "path": "resolver/pkg/testdata/cwf.yaml",
    "content": "apiVersion: apps/v1\nkind: CronWorkFlow\nmetadata:\n  name: aaa\n  namespace: stats-dev\nspec:\n  workflowSpec:\n    metadata:\n      labels:\n        app: app\n    templates:\n      container:\n        image: helloworld-image\n"
  },
  {
    "path": "resolver/pkg/testdata/digest.expected.yaml",
    "content": "deployment-templates:\n  deployment:\n    containers:\n      hello:\n        image:\n          digest: sha256:{{@@//hello-world:image.digest}}\nkind: CustomDeployment\nmetadata:\n  name: hellow-world\n  namespace: helm\n"
  },
  {
    "path": "resolver/pkg/testdata/digest.yaml",
    "content": "deployment-templates:\n  deployment:\n    containers:\n      hello:\n        image:\n          digest: \"sha256:{{@@//hello-world:image.digest}}\"\nkind: CustomDeployment\nmetadata:\n  name: hellow-world\n  namespace: helm\n"
  },
  {
    "path": "resolver/pkg/testdata/emptyinit.expected.yaml",
    "content": "apiVersion: apps/v1\nkind: CronWorkFlow\nmetadata:\n  name: aaa\n  namespace: stats-dev\nspec:\n  workflowSpec:\n    metadata:\n      labels:\n        app: app\n    templates:\n      container:\n        image: docker.io/kube/hello/image:tag\n      initContainers: null\n"
  },
  {
    "path": "resolver/pkg/testdata/emptyinit.yaml",
    "content": "apiVersion: apps/v1\nkind: CronWorkFlow\nmetadata:\n  name: aaa\n  namespace: stats-dev\nspec:\n  workflowSpec:\n    metadata:\n      labels:\n        app: app\n    templates:\n      initContainers:\n      container:\n        image: helloworld-image\n"
  },
  {
    "path": "resolver/pkg/testdata/flinkapp.expected.yaml",
    "content": "apiVersion: flink.k8s.io/v1beta1\nkind: FlinkApplication\nmetadata:\n  name: wordcount-operator-example\nspec:\n  image: docker.io/kube/flink/image:tag\n"
  },
  {
    "path": "resolver/pkg/testdata/flinkapp.yaml",
    "content": "apiVersion: flink.k8s.io/v1beta1\nkind: FlinkApplication\nmetadata:\n  name: wordcount-operator-example\nspec:\n  image: flinkapp-image\n"
  },
  {
    "path": "resolver/pkg/testdata/happypath.expected.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app: prometheus2\n  name: prometheus2\n  namespace: rtb-prod\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: prometheus2\n  strategy:\n    type: Recreate\n  template:\n    metadata:\n      labels:\n        app: prometheus2\n      name: prometheus2\n    spec:\n      containers:\n      - args:\n        - -logtostderr\n        - -zk=rtb-zk01.us-east-1a.public:2181,rtb-zk02.us-east-1a.public:2181,rtb-zk03.us-east-1a.public:2181,rtb-zk04.us-east-1a.public:2181,rtb-zk05.us-east-1a.public:2181\n        - -ofile=/etc/prometheus-sa/sa-rtb.yaml\n        - /sa/presence/rtb\n        image: docker.io/rtb/sacli/cmd/salist/image@sha256:5711bcf54511ab2fef6e08d9c9f9ae3f3a269e66834048465cc7502adb0d489b\n        name: samonitor\n        volumeMounts:\n        - mountPath: /etc/prometheus-sa\n          name: shared-data\n      - args:\n        - --storage.tsdb.path=/data/\n        - --storage.tsdb.retention=360h\n        - --web.enable-lifecycle\n        - --web.console.libraries=/etc/prometheus/console_libraries\n        - --web.console.templates=/etc/prometheus/consoles\n        - --config.file=/etc/prometheus-configs/prometheus.config\n        - --web.external-url=https://prometheus.rtb-prod.us-east-1.k8s.tubemogul.info/\n        image: prom/prometheus:v2.2.1\n        livenessProbe:\n          httpGet:\n            path: /-/healthy\n            port: 9090\n            scheme: HTTP\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: prometheus\n        ports:\n        - containerPort: 9090\n          name: webui\n        readinessProbe:\n          httpGet:\n            path: /-/ready\n            port: 9090\n            scheme: HTTP\n        resources:\n          limits:\n            cpu: \"10\"\n            memory: 22Gi\n          requests:\n            cpu: \"10\"\n            memory: 22Gi\n        volumeMounts:\n        - mountPath: /etc/prometheus-sa\n          name: shared-data\n        - mountPath: /etc/prometheus-configs\n          name: config-volume\n        - mountPath: /data\n          name: data-volume\n      - args:\n        - -file=/etc/prometheus-configs/..data\n        - -notify-http-method=POST\n        - -notify-http-url=http://127.0.0.1:9090/-/reload\n        image: docker.io/kube/filewatcher/image:tag\n        name: filewatcher\n        resources:\n          limits:\n            cpu: 0.01\n            memory: 10Mi\n          requests:\n            cpu: 0.01\n            memory: 10Mi\n        volumeMounts:\n        - mountPath: /etc/prometheus-configs\n          name: config-volume\n      securityContext:\n        fsGroup: 99\n        runAsUser: 99\n      volumes:\n      - emptyDir: {}\n        name: shared-data\n      - configMap:\n          name: prometheus2\n        name: config-volume\n      - name: data-volume\n        persistentVolumeClaim:\n          claimName: prometheus2-data\n"
  },
  {
    "path": "resolver/pkg/testdata/happypath.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  labels:\n    app: prometheus2\n  name: prometheus2\n  namespace: rtb-prod\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: prometheus2\n  strategy:\n    type: Recreate\n  template:\n    metadata:\n      labels:\n        app: prometheus2\n      name: prometheus2\n    spec:\n      containers:\n      - args:\n        - -logtostderr\n        - -zk=rtb-zk01.us-east-1a.public:2181,rtb-zk02.us-east-1a.public:2181,rtb-zk03.us-east-1a.public:2181,rtb-zk04.us-east-1a.public:2181,rtb-zk05.us-east-1a.public:2181\n        - -ofile=/etc/prometheus-sa/sa-rtb.yaml\n        - /sa/presence/rtb\n        image: salist\n        name: samonitor\n        volumeMounts:\n        - mountPath: /etc/prometheus-sa\n          name: shared-data\n      - args:\n        - --storage.tsdb.path=/data/\n        - --storage.tsdb.retention=360h\n        - --web.enable-lifecycle\n        - --web.console.libraries=/etc/prometheus/console_libraries\n        - --web.console.templates=/etc/prometheus/consoles\n        - --config.file=/etc/prometheus-configs/prometheus.config\n        - --web.external-url=https://prometheus.rtb-prod.us-east-1.k8s.tubemogul.info/\n        image: prom/prometheus:v2.2.1\n        livenessProbe:\n          httpGet:\n            path: /-/healthy\n            port: 9090\n            scheme: HTTP\n          initialDelaySeconds: 10\n          timeoutSeconds: 1\n        name: prometheus\n        ports:\n        - containerPort: 9090\n          name: webui\n        readinessProbe:\n          httpGet:\n            path: /-/ready\n            port: 9090\n            scheme: HTTP\n        resources:\n          limits:\n            cpu: \"10\"\n            memory: 22Gi\n          requests:\n            cpu: \"10\"\n            memory: 22Gi\n        volumeMounts:\n        - mountPath: /etc/prometheus-sa\n          name: shared-data\n        - mountPath: /etc/prometheus-configs\n          name: config-volume\n        - mountPath: /data\n          name: data-volume\n      - args:\n        - -file=/etc/prometheus-configs/..data\n        - -notify-http-method=POST\n        - -notify-http-url=http://127.0.0.1:9090/-/reload\n        image: filewatcher\n        name: filewatcher\n        resources:\n          limits:\n            cpu: 0.01\n            memory: 10Mi\n          requests:\n            cpu: 0.01\n            memory: 10Mi\n        volumeMounts:\n        - mountPath: /etc/prometheus-configs\n          name: config-volume\n      securityContext:\n        fsGroup: 99\n        runAsUser: 99\n      volumes:\n      - emptyDir: {}\n        name: shared-data\n      - configMap:\n          name: prometheus2\n        name: config-volume\n      - name: data-volume\n        persistentVolumeClaim:\n          claimName: prometheus2-data\n"
  },
  {
    "path": "resolver/pkg/testdata/zk.expected.yaml",
    "content": "apiVersion: zookeeper.pravega.io/v1beta1\nkind: ZookeeperCluster\nmetadata:\n  name: zk-cluster\nspec:\n  image:\n    repository: repository\n    tag: 1"
  },
  {
    "path": "resolver/pkg/testdata/zk.yaml",
    "content": "apiVersion: zookeeper.pravega.io/v1beta1\nkind: ZookeeperCluster\nmetadata:\n  name: zk-cluster\nspec:\n  image:\n    repository: repository\n    tag: 1"
  },
  {
    "path": "resolver/resolver.go",
    "content": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\npackage main\n\nimport (\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\n\tresolver \"github.com/adobe/rules_gitops/resolver/pkg\"\n)\n\ntype imagesFlags map[string]string\n\nfunc (i *imagesFlags) String() string {\n\treturn fmt.Sprintf(\"%v\", *i)\n}\n\nfunc (i *imagesFlags) Set(value string) error {\n\tv := strings.SplitN(value, \"=\", 2)\n\tif len(v) != 2 {\n\t\treturn errors.New(\"image parameter should be in form imagename=imagevalue\")\n\t}\n\t(*i)[strings.TrimSpace(v[0])] = strings.TrimSpace(v[1])\n\treturn nil\n}\n\nvar (\n\tinf    = flag.String(\"infile\", \"\", \"Input file\")\n\toutf   = flag.String(\"outfile\", \"\", \"Out file\")\n\timages = make(imagesFlags)\n)\n\nfunc main() {\n\tflag.Var(&images, \"image\", \"imagename=imagevalue\")\n\tflag.Parse()\n\tinfile := os.Stdin\n\tif *inf != \"\" {\n\t\tf, err := os.Open(*inf)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Unable to open file %s for reading: %s\", *inf, err)\n\t\t}\n\t\tdefer f.Close()\n\t\tinfile = f\n\t}\n\toutfile := os.Stdout\n\tif *outf != \"\" {\n\t\tf, err := os.Create(*outf)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Unable to create file %s for reading: %s\", *outf, err)\n\t\t}\n\t\tdefer f.Close()\n\t\toutfile = f\n\t}\n\n\terr := resolver.ResolveImages(infile, outfile, images)\n\tif err != nil {\n\t\tlog.Fatalf(\"Unable to process: %s\", err)\n\t}\n\n}\n"
  },
  {
    "path": "stamper/BUILD.bazel",
    "content": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\n\n# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License. You may obtain a copy\n# of the License at http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software distributed under\n# the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n# OF ANY KIND, either express or implied. See the License for the specific language\n# governing permissions and limitations under the License.\n\nload(\"@rules_go//go:def.bzl\", \"go_binary\", \"go_library\")\nload(\":stamp.bzl\", \"more_stable_status\", \"stamp_value\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"main.go\"],\n    importpath = \"github.com/adobe/rules_gitops/stamper\",\n    visibility = [\"//visibility:private\"],\n    deps = [\"//templating/fasttemplate:go_default_library\"],\n)\n\ngo_binary(\n    name = \"stamper\",\n    embed = [\":go_default_library\"],\n    visibility = [\"//:__subpackages__\"],\n)\n\nstamp_value(\n    name = \"build_user_value\",\n    str = \"{BUILD_USER}\",\n    visibility = [\"//:__subpackages__\"],\n)\n\nmore_stable_status(\n    name = \"more_stable_status\",\n    vars = [\n        \"BUILD_USER\",\n    ],\n    visibility = [\"//:__subpackages__\"],\n)\n\nbzl_library(\n    name = \"stamp\",\n    srcs = [\"stamp.bzl\"],\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "stamper/main.go",
    "content": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\npackage main\n\nimport (\n\t\"flag\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/adobe/rules_gitops/templating/fasttemplate\"\n)\n\ntype arrayFlags []string\n\nfunc (i *arrayFlags) String() string {\n\treturn \"\"\n}\n\nfunc (i *arrayFlags) Set(value string) error {\n\t*i = append(*i, value)\n\treturn nil\n}\n\nvar (\n\tstampInfoFile      arrayFlags\n\toutput             string\n\tformat, formatFile string\n)\n\nfunc init() {\n\tflag.Var(&stampInfoFile, \"stamp-info-file\", \"Paths to info_file and version_file files for stamping.\")\n\tflag.StringVar(&output, \"output\", \"\", \"The output file\")\n\tflag.StringVar(&formatFile, \"format-file\", \"\", \"The file containing stamp variables placeholders\")\n\tflag.StringVar(&format, \"format\", \"\", \"The format string containing stamp variables\")\n}\n\nfunc workspaceStatusDict(filenames []string) map[string]interface{} {\n\td := map[string]interface{}{}\n\tfor _, f := range filenames {\n\t\tcontent, err := ioutil.ReadFile(f)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Unable to read %s: %v\", f, err)\n\t\t}\n\t\tfor _, l := range strings.Split(string(content), \"\\n\") {\n\t\t\tsv := strings.SplitN(l, \" \", 2)\n\t\t\tif len(sv) == 2 {\n\t\t\t\td[sv[0]] = sv[1]\n\t\t\t}\n\t\t}\n\t}\n\treturn d\n}\n\nfunc main() {\n\tvar err error\n\tflag.Parse()\n\tstamps := workspaceStatusDict(stampInfoFile)\n\tif formatFile != \"\" {\n\t\tif format != \"\" {\n\t\t\tlog.Fatal(\"only one of --format or --format-file should be used\")\n\t\t}\n\t\timp, err := ioutil.ReadFile(formatFile)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Unable to read file %s: %v\", formatFile, err)\n\t\t}\n\t\tformat = string(imp)\n\t}\n\n\toutf := os.Stdout\n\tif output != \"\" {\n\t\toutf, err = os.OpenFile(output, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Unable to create output file %s: %v\", output, err)\n\t\t}\n\t\tdefer outf.Close()\n\t}\n\t_, err = fasttemplate.Execute(format, \"{\", \"}\", outf, stamps)\n\tif err != nil {\n\t\tlog.Fatalf(\"Unable to execute template %s: %v\", format, err)\n\t}\n}\n"
  },
  {
    "path": "stamper/stamp.bzl",
    "content": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License. You may obtain a copy\n# of the License at http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software distributed under\n# the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n# OF ANY KIND, either express or implied. See the License for the specific language\n# governing permissions and limitations under the License.\n\n\"\"\"Stamping utilities for replacing placeholders in strings.\"\"\"\n\ndef stamp(ctx, string, _tmpfiles, tmpfilename):\n    \"\"\"Stamp provided string replacing placeholders like {BUILD_USER}.\n\n    Uses an optimization shortcut for BUILD_USER\n\n    Args:\n        ctx: The rule context.\n        string: The string containing placeholders to stamp.\n        _tmpfiles: Unused, kept for API compatibility.\n        tmpfilename: Base name for temporary files.\n\n    Returns:\n        a string suitable for inclusion into bash script.\n    \"\"\"\n    deps = []\n\n    if \"{BUILD_USER}\" in string and \"{\" not in string.format(BUILD_USER = \"\"):\n        # shortcut for only {BUILD_USER} in placeholders\n        string = string.format(\n            BUILD_USER = \"$(cat %s)\" % ctx.file._build_user_value.path,\n        )\n\n        deps.append(ctx.files._build_user_value[0])\n        return string, deps\n\n    stamps = [ctx.file._info_file]\n    stamp_args = [\n        \"--stamp-info-file=%s\" % sf.path\n        for sf in stamps\n    ]\n    tmp_out_file = ctx.actions.declare_file(tmpfilename)\n\n    ctx.actions.run(\n        executable = ctx.executable._stamper,\n        arguments = [\n            \"--format=%s\" % string,\n            \"--output=%s\" % tmp_out_file.path,\n        ] + stamp_args,\n        inputs = stamps,\n        outputs = [tmp_out_file],\n        mnemonic = \"Stamp\",\n        tools = [ctx.executable._stamper],\n    )\n    string = \"$(cat {})\".format(tmp_out_file.path)\n    deps.append(tmp_out_file)\n    return string, deps\n\ndef _stamp_value_impl(ctx):\n    stamps = [ctx.file._info_file]\n    stamp_args = [\n        \"--stamp-info-file=%s\" % sf.path\n        for sf in stamps\n    ]\n    ctx.actions.run(\n        executable = ctx.executable._stamper,\n        arguments = [\n            \"--format=%s\" % ctx.attr.str,\n            \"--output=%s\" % ctx.outputs.out.path,\n        ] + stamp_args,\n        inputs = stamps,\n        outputs = [ctx.outputs.out],\n        mnemonic = \"Stamp\",\n        tools = [ctx.executable._stamper],\n    )\n\nstamp_value = rule(\n    implementation = _stamp_value_impl,\n    attrs = {\n        \"str\": attr.string(default = \"{BUILD_USER}\"),\n        \"_info_file\": attr.label(\n            default = Label(\"//stamper:more_stable_status.txt\"),\n            allow_single_file = True,\n        ),\n        \"_stamper\": attr.label(\n            default = Label(\"//stamper:stamper\"),\n            cfg = \"exec\",\n            executable = True,\n            allow_files = True,\n        ),\n    },\n    outputs = {\n        \"out\": \"%{name}.txt\",\n    },\n)\n\ndef _more_stable_status_impl(ctx):\n    v = \" \".join([\"-e ^\" + var for var in ctx.attr.vars])\n    ctx.actions.run_shell(\n        inputs = [ctx.info_file],\n        outputs = [ctx.outputs.out],\n        progress_message = \"Filtering stable status file\",\n        command = \"grep {} {} >{}\".format(v, ctx.info_file.path, ctx.outputs.out.path),\n    )\n\n# Generate reduced more stable version of stable-status.txt\n# Limited number of rows is extracted now to make it cacheable for CI/CD\nmore_stable_status = rule(\n    attrs = {\n        \"vars\": attr.string_list(\n            mandatory = True,\n            doc = \"Variables to extract from stable_status.txt\",\n        ),\n    },\n    outputs = {\n        \"out\": \"%{name}.txt\",\n    },\n    implementation = _more_stable_status_impl,\n)\n"
  },
  {
    "path": "templating/BUILD.bazel",
    "content": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License. You may obtain a copy\n# of the License at http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software distributed under\n# the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n# OF ANY KIND, either express or implied. See the License for the specific language\n# governing permissions and limitations under the License.\n\nload(\"@rules_go//go:def.bzl\", \"go_binary\", \"go_library\")\n\nlicenses([\"notice\"])  # Apache 2.0\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"main.go\"],\n    importpath = \"github.com/adobe/rules_gitops/templating\",\n    visibility = [\"//visibility:private\"],\n    deps = [\"//templating/fasttemplate:go_default_library\"],\n)\n\ngo_binary(\n    name = \"fast_template_engine\",\n    embed = [\":go_default_library\"],\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "templating/fasttemplate/BUILD.bazel",
    "content": "# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License. You may obtain a copy\n# of the License at http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software distributed under\n# the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n# OF ANY KIND, either express or implied. See the License for the specific language\n# governing permissions and limitations under the License.\n\nload(\"@rules_go//go:def.bzl\", \"go_library\", \"go_test\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"template.go\"],\n    importpath = \"github.com/adobe/rules_gitops/templating/fasttemplate\",\n    visibility = [\"//visibility:public\"],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    srcs = [\n        \"example_test.go\",\n        \"template_test.go\",\n    ],\n    embed = [\":go_default_library\"],\n)\n"
  },
  {
    "path": "templating/fasttemplate/LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015 Aliaksandr Valialkin\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n"
  },
  {
    "path": "templating/fasttemplate/README.md",
    "content": "fasttemplate\n============\n\nSimple and fast template engine for Go.\nForked from [fasttemplate](https://github.com/valyala/fasttemplate).\n\nThis package was modified from the original one:\n1. usage of unsafe is removed\n2. usage of buffer pools is removed\n\n*Please note that fasttemplate doesn't do any escaping on template values\nunlike [html/template](http://golang.org/pkg/html/template/) do. So values\nmust be properly escaped before passing them to fasttemplate.*\n\n"
  },
  {
    "path": "templating/fasttemplate/example_test.go",
    "content": "package fasttemplate\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/url\"\n)\n\nfunc ExampleTemplate() {\n\ttemplate := \"http://{{host}}/?foo={{bar}}{{bar}}&q={{query}}&baz={{baz}}\"\n\n\t// Substitution map.\n\t// Since \"baz\" tag is missing in the map, it will be left unchanged.\n\tm := map[string]interface{}{\n\t\t\"host\": \"google.com\",     // string - convenient\n\t\t\"bar\":  []byte(\"foobar\"), // byte slice - the fastest\n\n\t\t// TagFunc - flexible value. TagFunc is called only if the given\n\t\t// tag exists in the template.\n\t\t\"query\": TagFunc(func(w io.Writer, tag string) (int, error) {\n\t\t\treturn w.Write([]byte(url.QueryEscape(tag + \"=world\")))\n\t\t}),\n\t}\n\n\ts := ExecuteString(template, \"{{\", \"}}\", m)\n\tfmt.Printf(\"%s\", s)\n\n\t// Output:\n\t// http://google.com/?foo=foobarfoobar&q=query%3Dworld&baz={{baz}}\n}\n\nfunc ExampleTemplateWithSpaces() {\n\ttemplate := \"http://{{ host }}/?foo={{ bar }}{{ bar }}&q={{ query }}&baz={{ baz }}\"\n\n\t// Substitution map.\n\t// Since \"baz\" tag is missing in the map, it will be substituted\n\t// by an empty string.\n\tm := map[string]interface{}{\n\t\t\"host\": \"google.com\",     // string - convenient\n\t\t\"bar\":  []byte(\"foobar\"), // byte slice - the fastest\n\n\t\t// TagFunc - flexible value. TagFunc is called only if the given\n\t\t// tag exists in the template.\n\t\t\"query\": TagFunc(func(w io.Writer, tag string) (int, error) {\n\t\t\treturn w.Write([]byte(url.QueryEscape(tag + \"=world\")))\n\t\t}),\n\t}\n\n\ts := ExecuteString(template, \"{{\", \"}}\", m)\n\tfmt.Printf(\"%s\", s)\n\n\t// Output:\n\t// http://google.com/?foo=foobarfoobar&q=query%3Dworld&baz={{ baz }}\n}\n\nfunc ExampleTagFunc() {\n\ttemplate := \"foo[baz]bar\"\n\tbazSlice := [][]byte{[]byte(\"123\"), []byte(\"456\"), []byte(\"789\")}\n\tm := map[string]interface{}{\n\t\t// Always wrap the function into TagFunc.\n\t\t//\n\t\t// \"baz\" tag function writes bazSlice contents into w.\n\t\t\"baz\": TagFunc(func(w io.Writer, tag string) (int, error) {\n\t\t\tvar nn int\n\t\t\tfor _, x := range bazSlice {\n\t\t\t\tn, err := w.Write(x)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nn, err\n\t\t\t\t}\n\t\t\t\tnn += n\n\t\t\t}\n\t\t\treturn nn, nil\n\t\t}),\n\t}\n\n\ts := ExecuteString(template, \"[\", \"]\", m)\n\tfmt.Printf(\"%s\", s)\n\n\t// Output:\n\t// foo123456789bar\n}\n"
  },
  {
    "path": "templating/fasttemplate/template.go",
    "content": "// Package fasttemplate implements simple and fast template library.\n//\n// Fasttemplate is faster than text/template, strings.Replace\n// and strings.Replacer.\n//\n// Fasttemplate ideally fits for fast and simple placeholders' substitutions.\npackage fasttemplate\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"strings\"\n)\n\n// executeFunc calls f on each template tag (placeholder) occurrence.\n//\n// Returns the number of bytes written to w.\n//\n// This function is optimized for constantly changing templates.\n// Use Template.ExecuteFunc for frozen templates.\nfunc executeFunc(template, startTag, endTag string, w io.Writer, f TagFunc) (int64, error) {\n\tvar nn int64\n\tvar ni int\n\tvar err error\n\tfor {\n\t\tn := strings.Index(template, startTag)\n\t\tif n < 0 {\n\t\t\tbreak\n\t\t}\n\t\tni, err = w.Write([]byte(template[:n]))\n\t\tnn += int64(ni)\n\t\tif err != nil {\n\t\t\treturn nn, err\n\t\t}\n\n\t\ttemplate = template[n+len(startTag):]\n\t\tn = strings.Index(template, endTag)\n\t\tif n < 0 {\n\t\t\t// cannot find end tag - just write it to the output.\n\t\t\tni, err = w.Write([]byte(startTag))\n\t\t\tnn += int64(ni)\n\t\t\tif err != nil {\n\t\t\t\treturn nn, err\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t\ttag := template[:n]\n\t\tni, err = f(w, tag)\n\t\tnn += int64(ni)\n\t\tif err != nil {\n\t\t\tif err == missingTag {\n\t\t\t\tni, err = w.Write([]byte(startTag + tag + endTag))\n\t\t\t\tnn += int64(ni)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn nn, err\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn nn, err\n\t\t\t}\n\t\t}\n\t\ttemplate = template[n+len(endTag):]\n\t}\n\tni, err = w.Write([]byte(template))\n\tnn += int64(ni)\n\n\treturn nn, err\n}\n\n// Execute substitutes template tags (placeholders) with the corresponding\n// values from the map m and writes the result to the given writer w.\n//\n// Substitution map m may contain values with the following types:\n//   * []byte - the fastest value type\n//   * string - convenient value type\n//   * TagFunc - flexible value type\n//\n// Returns the number of bytes written to w.\n//\n// This function is optimized for constantly changing templates.\n// Use Template.Execute for frozen templates.\nfunc Execute(template, startTag, endTag string, w io.Writer, m map[string]interface{}) (int64, error) {\n\treturn executeFunc(template, startTag, endTag, w, func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })\n}\n\n// executeFuncString calls f on each template tag (placeholder) occurrence\n// and substitutes it with the data written to TagFunc's w.\n//\n// Returns the resulting string.\n//\n// This function is optimized for constantly changing templates.\n// Use Template.ExecuteFuncString for frozen templates.\nfunc executeFuncString(template, startTag, endTag string, f TagFunc) string {\n\ttagsCount := bytes.Count([]byte(template), []byte(startTag))\n\tif tagsCount == 0 {\n\t\treturn template\n\t}\n\n\tbb := &bytes.Buffer{}\n\tif _, err := executeFunc(template, startTag, endTag, bb, f); err != nil {\n\t\tpanic(fmt.Sprintf(\"unexpected error: %s\", err))\n\t}\n\treturn bb.String()\n}\n\n// ExecuteString substitutes template tags (placeholders) with the corresponding\n// values from the map m and returns the result.\n//\n// Substitution map m may contain values with the following types:\n//   * []byte - the fastest value type\n//   * string - convenient value type\n//   * TagFunc - flexible value type\n//\n// This function is optimized for constantly changing templates.\n// Use Template.ExecuteString for frozen templates.\nfunc ExecuteString(template, startTag, endTag string, m map[string]interface{}) string {\n\treturn executeFuncString(template, startTag, endTag, func(w io.Writer, tag string) (int, error) { return stdTagFunc(w, tag, m) })\n}\n\n// TagFunc can be used as a substitution value in the map passed to Execute*.\n// Execute* functions pass tag (placeholder) name in 'tag' argument.\n//\n// TagFunc must be safe to call from concurrently running goroutines.\n//\n// TagFunc must write contents to w and return the number of bytes written.\ntype TagFunc func(w io.Writer, tag string) (int, error)\n\nvar missingTag = errors.New(\"missing tag\")\n\nfunc stdTagFunc(w io.Writer, tag string, m map[string]interface{}) (int, error) {\n\ttag = strings.TrimSpace(tag)\n\tv, exists := m[tag]\n\tif !exists {\n\t\treturn 0, missingTag\n\t}\n\tif v == nil {\n\t\treturn 0, nil\n\t}\n\tswitch value := v.(type) {\n\tcase []byte:\n\t\treturn w.Write(value)\n\tcase string:\n\t\treturn w.Write([]byte(value))\n\tcase TagFunc:\n\t\treturn value(w, tag)\n\tdefault:\n\t\tpanic(fmt.Sprintf(\"tag=%q contains unexpected value type=%#v. Expected []byte, string or TagFunc\", tag, v))\n\t}\n}\n"
  },
  {
    "path": "templating/fasttemplate/template_test.go",
    "content": "package fasttemplate\n\nimport (\n\t\"bytes\"\n\t\"io\"\n\t\"testing\"\n)\n\nfunc TestExecuteFunc(t *testing.T) {\n\ttestExecuteFunc(t, \"\", \"\")\n\ttestExecuteFunc(t, \"a\", \"a\")\n\ttestExecuteFunc(t, \"abc\", \"abc\")\n\ttestExecuteFunc(t, \"{foo}\", \"xxxx\")\n\ttestExecuteFunc(t, \"a{foo}\", \"axxxx\")\n\ttestExecuteFunc(t, \"{foo}a\", \"xxxxa\")\n\ttestExecuteFunc(t, \"a{foo}bc\", \"axxxxbc\")\n\ttestExecuteFunc(t, \"{foo}{foo}\", \"xxxxxxxx\")\n\ttestExecuteFunc(t, \"{foo}{foo}\", \"xxxxxxxx\")\n\ttestExecuteFunc(t, \"{foo}bar{foo}\", \"xxxxbarxxxx\")\n\n\t// unclosed tag\n\ttestExecuteFunc(t, \"{unclosed\", \"{unclosed\")\n\ttestExecuteFunc(t, \"{{unclosed\", \"{{unclosed\")\n\ttestExecuteFunc(t, \"{un{closed\", \"{un{closed\")\n\n\t// test unknown tag\n\ttestExecuteFunc(t, \"{unknown}\", \"zz\")\n\ttestExecuteFunc(t, \"{foo}q{unexpected}{missing}bar{foo}\", \"xxxxqzzzzbarxxxx\")\n}\n\nfunc testExecuteFunc(t *testing.T, template, expectedOutput string) {\n\tvar bb bytes.Buffer\n\texecuteFunc(template, \"{\", \"}\", &bb, func(w io.Writer, tag string) (int, error) {\n\t\tif tag == \"foo\" {\n\t\t\treturn w.Write([]byte(\"xxxx\"))\n\t\t}\n\t\treturn w.Write([]byte(\"zz\"))\n\t})\n\n\toutput := string(bb.Bytes())\n\tif output != expectedOutput {\n\t\tt.Fatalf(\"unexpected output for template=%q: %q. Expected %q\", template, output, expectedOutput)\n\t}\n}\n\nfunc TestExecute(t *testing.T) {\n\ttestExecute(t, \"\", \"\")\n\ttestExecute(t, \"a\", \"a\")\n\ttestExecute(t, \"abc\", \"abc\")\n\ttestExecute(t, \"{foo}\", \"xxxx\")\n\ttestExecute(t, \"a{foo}\", \"axxxx\")\n\ttestExecute(t, \"{foo}a\", \"xxxxa\")\n\ttestExecute(t, \"a{foo}bc\", \"axxxxbc\")\n\ttestExecute(t, \"{foo}{foo}\", \"xxxxxxxx\")\n\ttestExecute(t, \"{foo}bar{foo}\", \"xxxxbarxxxx\")\n\n\t// unclosed tag\n\ttestExecute(t, \"{unclosed\", \"{unclosed\")\n\ttestExecute(t, \"{{unclosed\", \"{{unclosed\")\n\ttestExecute(t, \"{un{closed\", \"{un{closed\")\n\n\t// test unknown tag\n\ttestExecute(t, \"{unknown}\", \"{unknown}\")\n\ttestExecute(t, \"{foo}q{unexpected}{missing}bar{foo}\", \"xxxxq{unexpected}{missing}barxxxx\")\n\ttestExecute(t, \"{foo}q{ unexpected }{ missing }bar{foo}\", \"xxxxq{ unexpected }{ missing }barxxxx\")\n}\n\nfunc testExecute(t *testing.T, template, expectedOutput string) {\n\tvar bb bytes.Buffer\n\tExecute(template, \"{\", \"}\", &bb, map[string]interface{}{\"foo\": \"xxxx\"})\n\toutput := string(bb.Bytes())\n\tif output != expectedOutput {\n\t\tt.Fatalf(\"unexpected output for template=%q: %q. Expected %q\", template, output, expectedOutput)\n\t}\n}\n\nfunc TestExecuteString(t *testing.T) {\n\ttestExecuteString(t, \"\", \"\")\n\ttestExecuteString(t, \"a\", \"a\")\n\ttestExecuteString(t, \"abc\", \"abc\")\n\ttestExecuteString(t, \"{foo}\", \"xxxx\")\n\ttestExecuteString(t, \"a{foo}\", \"axxxx\")\n\ttestExecuteString(t, \"{foo}a\", \"xxxxa\")\n\ttestExecuteString(t, \"a{foo}bc\", \"axxxxbc\")\n\ttestExecuteString(t, \"{foo}{foo}\", \"xxxxxxxx\")\n\ttestExecuteString(t, \"{foo}bar{foo}\", \"xxxxbarxxxx\")\n\n\t// unclosed tag\n\ttestExecuteString(t, \"{unclosed\", \"{unclosed\")\n\ttestExecuteString(t, \"{{unclosed\", \"{{unclosed\")\n\ttestExecuteString(t, \"{un{closed\", \"{un{closed\")\n\n\t// test unknown tag\n\ttestExecuteString(t, \"{unknown}\", \"{unknown}\")\n\ttestExecuteString(t, \"{foo}q{unexpected}{missing}bar{foo}\", \"xxxxq{unexpected}{missing}barxxxx\")\n\ttestExecuteString(t, \"{foo}q{ unexpected }{ missing }bar{foo}\", \"xxxxq{ unexpected }{ missing }barxxxx\")\n}\n\nfunc testExecuteString(t *testing.T, template, expectedOutput string) {\n\toutput := ExecuteString(template, \"{\", \"}\", map[string]interface{}{\"foo\": \"xxxx\"})\n\tif output != expectedOutput {\n\t\tt.Fatalf(\"unexpected output for template=%q: %q. Expected %q\", template, output, expectedOutput)\n\t}\n}\n\nfunc expectPanic(t *testing.T, f func()) {\n\tdefer func() {\n\t\tif r := recover(); r == nil {\n\t\t\tt.Fatalf(\"missing panic\")\n\t\t}\n\t}()\n\tf()\n}\n"
  },
  {
    "path": "templating/main.go",
    "content": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\npackage main\n\nimport (\n\t\"flag\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"os\"\n\t\"strings\"\n\n\t\"github.com/adobe/rules_gitops/templating/fasttemplate\"\n)\n\ntype arrayFlags []string\n\nfunc (i *arrayFlags) String() string {\n\treturn \"\"\n}\n\nfunc (i *arrayFlags) Set(value string) error {\n\t*i = append(*i, value)\n\treturn nil\n}\n\nvar (\n\tstampInfoFile     arrayFlags\n\toutput, template  string\n\tvariable, imports arrayFlags\n\texecutable        bool\n\tstartTag, endTag  string\n)\n\nfunc init() {\n\tflag.Var(&stampInfoFile, \"stamp_info_file\", \"Paths to info_file and version_file files for stamping. Content of stamp_info_file files will be used to substitute variable values so --variable VAR={BUILD_USER}/value will result in {{VAR}} being expanded to builduser/value\")\n\tflag.Var(&variable, \"variable\", \"A variable to expand in the template, in the format NAME=VALUE\")\n\tflag.Var(&imports, \"imports\", \"A file to import as another template, in the format NAME=filename\")\n\tflag.StringVar(&output, \"output\", \"\", \"The output file\")\n\tflag.StringVar(&template, \"template\", \"\", \"The input file, mandatory\")\n\tflag.BoolVar(&executable, \"executable\", false, \"Whether to adds the executable bit to the output\")\n\tflag.StringVar(&startTag, \"start_tag\", \"{{\", \"Start tag for template placeholders\")\n\tflag.StringVar(&endTag, \"end_tag\", \"}}\", \"End tag for template placeholders\")\n}\n\nfunc workspaceStatusDict(filenames []string) map[string]interface{} {\n\td := map[string]interface{}{}\n\tfor _, f := range filenames {\n\t\tcontent, err := ioutil.ReadFile(f)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Unable to read %s: %v\", f, err)\n\t\t}\n\t\tfor _, l := range strings.Split(string(content), \"\\n\") {\n\t\t\tsv := strings.SplitN(l, \" \", 2)\n\t\t\tif len(sv) == 2 {\n\t\t\t\td[sv[0]] = sv[1]\n\t\t\t}\n\t\t}\n\t}\n\treturn d\n}\n\nfunc main() {\n\tvar err error\n\tflag.Parse()\n\tstamps := workspaceStatusDict(stampInfoFile)\n\tctx := map[string]interface{}{}\n\tfor _, v := range variable {\n\t\tsv := strings.SplitN(v, \"=\", 2)\n\t\tif len(sv) != 2 {\n\t\t\tlog.Fatalf(\"variable must be VAR=value, got %s\", v)\n\t\t}\n\t\tval := fasttemplate.ExecuteString(sv[1], \"{\", \"}\", stamps)\n\t\tctx[sv[0]] = val\n\t\tctx[\"variables.\"+sv[0]] = val\n\t}\n\n\tfor _, v := range imports {\n\t\tsv := strings.SplitN(v, \"=\", 2)\n\t\tif len(sv) != 2 {\n\t\t\tlog.Fatalf(\"imports must be VAR=filename, got %s\", v)\n\t\t}\n\t\timp, err := ioutil.ReadFile(sv[1])\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Unable to parse file %s: %v\", sv[1], err)\n\t\t}\n\t\tval := fasttemplate.ExecuteString(string(imp), startTag, endTag, ctx)\n\t\t// if err != nil {\n\t\t// \tlog.Fatalf(\"Unable to execute template %s: %v\", sv[1], err)\n\t\t// }\n\t\tctx[\"imports.\"+sv[0]] = fasttemplate.ExecuteString(val, \"{\", \"}\", stamps)\n\t}\n\n\tvar tpl []byte\n\tif template != \"\" {\n\t\ttpl, err = ioutil.ReadFile(template)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Unable to parse template %s: %v\", template, err)\n\t\t}\n\t} else {\n\t\ttpl, err = ioutil.ReadAll(os.Stdin)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Unable to parse template from stdin: %v\", err)\n\t\t}\n\t}\n\toutf := os.Stdout\n\tif output != \"\" {\n\t\tvar perm os.FileMode = 0666\n\t\tif executable {\n\t\t\tperm = 0777\n\t\t}\n\t\toutf, err = os.OpenFile(output, os.O_RDWR|os.O_CREATE|os.O_TRUNC, perm)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Unable to create output file %s: %v\", output, err)\n\t\t}\n\t\tdefer outf.Close()\n\t}\n\t_, err = fasttemplate.Execute(string(tpl), startTag, endTag, outf, ctx)\n\tif err != nil {\n\t\tlog.Fatalf(\"Unable to execute template %s: %v\", template, err)\n\t}\n}\n"
  },
  {
    "path": "templating/testdata/generated",
    "content": "generated data here, {{VAR}}"
  },
  {
    "path": "templating/testdata/stable-status.txt",
    "content": "BUILD_EMBED_LABEL \nBUILD_HOST pesterni-macOS-1\nBUILD_USER pesterni\n"
  },
  {
    "path": "templating/testdata/template1.tpl",
    "content": "Welcome, {{VAR}}!\n{{imports.IMP}}\n"
  },
  {
    "path": "templating/testdata/volatile-status.txt",
    "content": "BUILD_SCM_REVISION cc45ebf5a259abf555a73ba9f751d954c4d26612\nBUILD_SCM_SHORT_REVISION cc45ebf5a2\nBUILD_SCM_STATUS Modified\nBUILD_TIMESTAMP 1547146348\n"
  },
  {
    "path": "testing/BUILD.bazel",
    "content": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\n\nbzl_library(\n    name = \"defs\",\n    srcs = [\"defs.bzl\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"//testing/private:k8s_test_namespace\",\n        \"//testing/private:k8s_test_setup\",\n    ],\n)\n"
  },
  {
    "path": "testing/defs.bzl",
    "content": "\"\"\"Public API for Kubernetes testing rules.\"\"\"\n\nload(\"//testing/private:k8s_test_namespace.bzl\", _k8s_test_namespace = \"k8s_test_namespace\")\nload(\"//testing/private:k8s_test_setup.bzl\", _k8s_test_setup = \"k8s_test_setup\")\n\nk8s_test_namespace = _k8s_test_namespace\nk8s_test_setup = _k8s_test_setup\n"
  },
  {
    "path": "testing/it_manifest_filter/BUILD.bazel",
    "content": "load(\"@rules_go//go:def.bzl\", \"go_binary\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"it_manifest_filter.go\"],\n    importpath = \"github.com/adobe/rules_gitops/testing/it_manifest_filter\",\n    visibility = [\"//visibility:private\"],\n    deps = [\"//testing/it_manifest_filter/pkg:go_default_library\"],\n)\n\ngo_binary(\n    name = \"it_manifest_filter\",\n    embed = [\":go_default_library\"],\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "testing/it_manifest_filter/it_manifest_filter.go",
    "content": "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\nvar (\n\tinf  = flag.String(\"infile\", \"\", \"Input file\")\n\toutf = flag.String(\"outfile\", \"\", \"Out file\")\n)\n\nfunc main() {\n\tflag.Parse()\n\tinfile := os.Stdin\n\tif *inf != \"\" {\n\t\tf, err := os.Open(*inf)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Unable to open file %s for reading: %s\", *inf, err)\n\t\t}\n\t\tdefer f.Close()\n\t\tinfile = f\n\t}\n\toutfile := os.Stdout\n\tif *outf != \"\" {\n\t\tf, err := os.Create(*outf)\n\t\tif err != nil {\n\t\t\tlog.Fatalf(\"Unable to create file %s for reading: %s\", *outf, err)\n\t\t}\n\t\tdefer f.Close()\n\t\toutfile = f\n\t}\n\n\terr := filter.ReplacePDWithEmptyDirs(infile, outfile)\n\tif err != nil {\n\t\tlog.Fatalf(\"Unable to process: %s\", err)\n\t}\n\n}\n"
  },
  {
    "path": "testing/it_manifest_filter/pkg/BUILD.bazel",
    "content": "load(\"@rules_go//go:def.bzl\", \"go_library\", \"go_test\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"filter.go\"],\n    importpath = \"github.com/adobe/rules_gitops/testing/it_manifest_filter/pkg\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@com_github_ghodss_yaml//:go_default_library\",\n        \"@io_k8s_api//apps/v1:go_default_library\",\n        \"@io_k8s_api//core/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1/unstructured:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/runtime:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/util/yaml:go_default_library\",\n        \"@io_k8s_client_go//util/jsonpath:go_default_library\",\n    ],\n)\n\ngo_test(\n    name = \"go_default_test\",\n    srcs = [\"filter_test.go\"],\n    data = glob([\"testdata/**\"]),\n    deps = [\n        \":go_default_library\",\n        \"@com_github_google_go_cmp//cmp:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "testing/it_manifest_filter/pkg/filter.go",
    "content": "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\tv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/util/yaml\"\n\t\"k8s.io/client-go/util/jsonpath\"\n)\n\n// ReplacePDWithEmptyDirs reads yaml or json stream from in, deserialize it and replace references to all PVC volumes with EmptyDir\n// remove all PersistemtVolumeClaim objects\n// then serialize it back into out stream.\nfunc ReplacePDWithEmptyDirs(in io.Reader, out io.Writer) error {\n\tdecoder := yaml.NewYAMLOrJSONDecoder(in, 1024)\n\tvar err error\n\tfirstObj := true\n\tfor err == nil || isEmptyYamlError(err) {\n\t\tvar obj unstructured.Unstructured\n\t\terr = decoder.Decode(&obj)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif obj.GetName() == \"\" {\n\t\t\treturn fmt.Errorf(\"Missing metadata.name in object %v\", obj)\n\t\t}\n\t\tif obj.GetKind() == \"\" {\n\t\t\treturn fmt.Errorf(\"Missing kind in object %v\", obj)\n\t\t}\n\t\tif obj.GetKind() == \"PersistentVolumeClaim\" {\n\t\t\tcontinue // skip all PVCs\n\t\t}\n\t\tif obj.GetKind() == \"Ingress\" {\n\t\t\tcontinue // skip all Ingress objects\n\t\t}\n\t\tif obj.GetKind() == \"StatefulSet\" && obj.GetAPIVersion() == \"apps/v1\" {\n\t\t\tvar statefulset appsv1.StatefulSet\n\t\t\terr = runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &statefulset)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"Unable to decode statefulset object %s\", obj.GetName())\n\t\t\t}\n\t\t\tprocessStatefulSet(&statefulset)\n\t\t\tobj.Object, err = runtime.DefaultUnstructuredConverter.ToUnstructured(&statefulset)\n\t\t\tif err != nil {\n\t\t\t\treturn fmt.Errorf(\"Unable to convert statefulset to unstructured: %v\", err)\n\t\t\t}\n\t\t\tdelete(obj.Object, \"status\")\n\t\t}\n\t\tif obj.GetKind() == \"Certificate\" {\n\t\t\tfindAndReplaceIssuerName(obj.Object)\n\t\t}\n\n\t\tfindAndReplacePVC(obj.Object)\n\t\tbuf, err := yamlenc.Marshal(obj.Object)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Unable to marshal object %v\", obj.Object)\n\t\t}\n\t\tif firstObj {\n\t\t\tfirstObj = false\n\t\t} else {\n\t\t\t_, err = out.Write([]byte(\"---\\n\"))\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t\t_, err = out.Write(buf)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t}\n\tif err != io.EOF {\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc isEmptyYamlError(err error) bool {\n\treturn strings.Contains(err.Error(), \"is missing in 'null'\")\n}\n\nvar emptydirSpec = make(map[string]interface{})\n\n/*\n findAndReplaceTag replaces the image tags inside one object\n It searches the object for container session\n then loops though all images inside containers session, finds matched ones and update the tag name\n*/\nfunc findAndReplacePVC(obj map[string]interface{}) {\n\tfound := false\n\t_, found = obj[\"persistentVolumeClaim\"]\n\tif found {\n\t\tdelete(obj, \"persistentVolumeClaim\")\n\t\tobj[\"emptyDir\"] = emptydirSpec\n\t}\n\tif !found {\n\t\tfindPVC(obj)\n\t}\n}\n\nfunc findPVC(obj map[string]interface{}) {\n\tfor key := range obj {\n\t\tswitch typedV := obj[key].(type) {\n\t\tcase map[string]interface{}:\n\t\t\tfindAndReplacePVC(typedV)\n\t\tcase []interface{}:\n\t\t\tfor i := range typedV {\n\t\t\t\titem := typedV[i]\n\t\t\t\ttypedItem, ok := item.(map[string]interface{})\n\t\t\t\tif ok {\n\t\t\t\t\tfindAndReplacePVC(typedItem)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc processStatefulSet(obj *appsv1.StatefulSet) {\n\tif len(obj.Spec.VolumeClaimTemplates) == 0 {\n\t\treturn\n\t}\n\t//collect existing volumes\n\texistingVolumes := make(map[string]int)\n\tfor i, v := range obj.Spec.Template.Spec.Volumes {\n\t\texistingVolumes[v.Name] = i\n\t}\n\tfor _, vct := range obj.Spec.VolumeClaimTemplates {\n\t\tname := vct.GetObjectMeta().GetName()\n\t\tvol := v1.Volume{\n\t\t\tName: name,\n\t\t\tVolumeSource: v1.VolumeSource{\n\t\t\t\tEmptyDir: &v1.EmptyDirVolumeSource{},\n\t\t\t},\n\t\t}\n\t\tif storage, ok := vct.Spec.Resources.Requests[\"storage\"]; ok {\n\t\t\tvol.VolumeSource.EmptyDir.SizeLimit = &storage\n\t\t}\n\t\tif i, ok := existingVolumes[name]; ok {\n\t\t\tobj.Spec.Template.Spec.Volumes[i] = vol\n\t\t} else {\n\t\t\tobj.Spec.Template.Spec.Volumes = append(obj.Spec.Template.Spec.Volumes, vol)\n\t\t}\n\t}\n\tobj.Spec.VolumeClaimTemplates = nil\n}\n\nfunc findAndReplaceIssuerName(obj map[string]interface{}) {\n\tj := jsonpath.New(\"cert_issuer_name\")\n\terr := j.Parse(`{.spec.issuerRef}`)\n\tif err != nil {\n\t\tlog.Fatalln(\"Unable to parse jsonpath: \", err)\n\t}\n\tres, err := j.FindResults(obj)\n\tif err != nil {\n\t\tlog.Println(\"Unable to find jsonpath: \", err)\n\t\treturn\n\t}\n\tissuerRef := res[0][0].Interface().(map[string]interface{})\n\tif issuerRef[\"name\"] == \"letsencrypt-prod\" {\n\t\tissuerRef[\"name\"] = \"letsencrypt-staging\"\n\t}\n}\n"
  },
  {
    "path": "testing/it_manifest_filter/pkg/filter_test.go",
    "content": "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_gitops/testing/it_manifest_filter/pkg\"\n\t\"github.com/google/go-cmp/cmp\"\n)\n\nfunc TestHappyPath(t *testing.T) {\n\ttestcases := []string{\"happypath\", \"statefulset\", \"statefulset2\", \"certificate\"}\n\tfor _, testcase := range testcases {\n\t\tt.Run(testcase, func(t *testing.T) {\n\t\t\tinfn := fmt.Sprintf(\"testdata/%s.yaml\", testcase)\n\t\t\texpectedfn := fmt.Sprintf(\"testdata/%s.expected.yaml\", testcase)\n\t\t\tinf, err := os.Open(infn)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Unable to open file %s\", infn)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdefer inf.Close()\n\t\t\texpectedb, err := ioutil.ReadFile(expectedfn)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Unable to read file %s\", expectedfn)\n\t\t\t\treturn\n\t\t\t}\n\t\t\texpected := strings.TrimSpace(string(expectedb))\n\t\t\tvar outbuf bytes.Buffer\n\t\t\terr = filter.ReplacePDWithEmptyDirs(inf, &outbuf)\n\t\t\tif err != nil {\n\t\t\t\tt.Errorf(\"Unexpected error %v\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif diff := cmp.Diff(expected, strings.TrimSpace(outbuf.String())); diff != \"\" {\n\t\t\t\tt.Errorf(\"Unexpected output (-want +got):\\n%s\", diff)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "testing/it_manifest_filter/pkg/testdata/certificate.expected.yaml",
    "content": "apiVersion: certmanager.k8s.io/v1alpha1\nkind: Certificate\nmetadata:\n  name: actv-notification-certificate\n  namespace: namespace\nspec:\n  acme:\n    config:\n    - dns01:\n        provider: route53\n      domains:\n      - actv-notification-service.@k8s.namespace@.@k8s.cluster@.k8s.tubemogul.info\n  commonName: actv-notification-service.@k8s.namespace@.@k8s.cluster@.k8s.tubemogul.info\n  dnsNames:\n  - actv-notification-service.@k8s.namespace@.@k8s.cluster@.k8s.tubemogul.info\n  issuerRef:\n    kind: ClusterIssuer\n    name: letsencrypt-staging\n  secretName: actv-notification-tls\n"
  },
  {
    "path": "testing/it_manifest_filter/pkg/testdata/certificate.yaml",
    "content": "apiVersion: certmanager.k8s.io/v1alpha1\nkind: Certificate\nmetadata:\n  name: actv-notification-certificate\n  namespace: namespace\nspec:\n  secretName: actv-notification-tls\n  issuerRef:\n    name: letsencrypt-prod\n    kind: ClusterIssuer\n  commonName: actv-notification-service.@k8s.namespace@.@k8s.cluster@.k8s.tubemogul.info\n  dnsNames:\n    - actv-notification-service.@k8s.namespace@.@k8s.cluster@.k8s.tubemogul.info\n  acme:\n    config:\n      - dns01:\n          provider: route53\n        domains:\n          - actv-notification-service.@k8s.namespace@.@k8s.cluster@.k8s.tubemogul.info\n"
  },
  {
    "path": "testing/it_manifest_filter/pkg/testdata/happypath.expected.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: mysql\n  namespace: pesterni\nspec:\n  ports:\n  - port: 3306\n  selector:\n    app: mariadb\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: mariadb\n  namespace: pesterni\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: mariadb\n  strategy:\n    type: Recreate\n  template:\n    metadata:\n      labels:\n        app: mariadb\n    spec:\n      containers:\n      - env:\n        - name: MYSQL_ROOT_PASSWORD\n          valueFrom:\n            secretKeyRef:\n              key: password\n              name: mysql-secret\n        image: cr.k8s.tubemogul.info/pesterni/db/image@sha256:f4504709ea0a2f3a8f0ba29de5ef7cdc6e3be1c4411d39a60058861c16e6a5aa\n        name: mariadb\n        ports:\n        - containerPort: 3306\n          name: mysql\n        readinessProbe:\n          exec:\n            command:\n            - mysql\n            - -h\n            - 127.0.0.1\n            - -e\n            - SELECT 1\n          initialDelaySeconds: 10\n          periodSeconds: 10\n          timeoutSeconds: 1\n        resources:\n          limits:\n            cpu: 1000m\n            memory: 1Gi\n          requests:\n            cpu: 10m\n            memory: 1Gi\n        volumeMounts:\n        - mountPath: /data\n          name: data-volume\n      volumes:\n      - emptyDir: {}\n        name: data-volume\n"
  },
  {
    "path": "testing/it_manifest_filter/pkg/testdata/happypath.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: mysql\n  namespace: pesterni\nspec:\n  ports:\n  - port: 3306\n  selector:\n    app: mariadb\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: mariadb\n  namespace: pesterni\nspec:\n  replicas: 1\n  selector:\n    matchLabels:\n      app: mariadb\n  strategy:\n    type: Recreate\n  template:\n    metadata:\n      labels:\n        app: mariadb\n    spec:\n      containers:\n      - env:\n        - name: MYSQL_ROOT_PASSWORD\n          valueFrom:\n            secretKeyRef:\n              key: password\n              name: mysql-secret\n        image: cr.k8s.tubemogul.info/pesterni/db/image@sha256:f4504709ea0a2f3a8f0ba29de5ef7cdc6e3be1c4411d39a60058861c16e6a5aa\n        name: mariadb\n        ports:\n        - containerPort: 3306\n          name: mysql\n        readinessProbe:\n          exec:\n            command:\n            - mysql\n            - -h\n            - 127.0.0.1\n            - -e\n            - SELECT 1\n          initialDelaySeconds: 10\n          periodSeconds: 10\n          timeoutSeconds: 1\n        resources:\n          limits:\n            cpu: 1000m\n            memory: 1Gi\n          requests:\n            cpu: 10m\n            memory: 1Gi\n        volumeMounts:\n        - mountPath: /data\n          name: data-volume\n      volumes:\n      - name: data-volume\n        persistentVolumeClaim:\n          claimName: mariadb-data\n---\napiVersion: v1\nkind: PersistentVolumeClaim\nmetadata:\n  annotations:\n    volume.beta.kubernetes.io/storage-class: standard\n  name: mariadb-data\n  namespace: pesterni\nspec:\n  accessModes:\n  - ReadWriteOnce\n  resources:\n    requests:\n      storage: 5Gi\n"
  },
  {
    "path": "testing/it_manifest_filter/pkg/testdata/statefulset.expected.yaml",
    "content": "apiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  creationTimestamp: null\n  name: zk\n  namespace: actv-apps-uat\nspec:\n  podManagementPolicy: Parallel\n  replicas: 3\n  selector:\n    matchLabels:\n      app: zk\n  serviceName: zk-hs\n  template:\n    metadata:\n      creationTimestamp: null\n      labels:\n        app: zk\n    spec:\n      affinity:\n        podAntiAffinity:\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: app\n                operator: In\n                values:\n                - zk\n            topologyKey: kubernetes.io/hostname\n      containers:\n      - command:\n        - sh\n        - -c\n        - start-zookeeper --servers=3 --data_dir=/var/lib/zookeeper/data --data_log_dir=/var/lib/zookeeper/data\n          --conf_dir=/opt/zookeeper/conf --client_port=2181 --election_port=3888 --server_port=2888\n          --tick_time=2000 --init_limit=10 --sync_limit=5 --heap=512M --max_client_cnxns=60\n          --snap_retain_count=3 --purge_interval=1 --max_session_timeout=40000 --min_session_timeout=4000\n          --log_level=ERROR\n        image: k8s.gcr.io/kubernetes-zookeeper:1.0-3.4.10\n        livenessProbe:\n          exec:\n            command:\n            - sh\n            - -c\n            - zookeeper-ready 2181\n          initialDelaySeconds: 10\n          timeoutSeconds: 5\n        name: kubernetes-zookeeper\n        ports:\n        - containerPort: 2181\n          name: client\n        - containerPort: 2888\n          name: server\n        - containerPort: 3888\n          name: leader-election\n        readinessProbe:\n          exec:\n            command:\n            - sh\n            - -c\n            - zookeeper-ready 2181\n          initialDelaySeconds: 10\n          timeoutSeconds: 5\n        resources:\n          requests:\n            cpu: 500m\n            memory: 1Gi\n        volumeMounts:\n        - mountPath: /var/lib/zookeeper\n          name: datadir\n      securityContext:\n        fsGroup: 1000\n        runAsUser: 1000\n      volumes:\n      - emptyDir:\n          sizeLimit: 5Gi\n        name: datadir\n  updateStrategy:\n    type: RollingUpdate\n---\napiVersion: policy/v1beta1\nkind: PodDisruptionBudget\nmetadata:\n  name: zk-pdb\n  namespace: actv-apps-uat\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      app: zk\n"
  },
  {
    "path": "testing/it_manifest_filter/pkg/testdata/statefulset.yaml",
    "content": "apiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: zk\n  namespace: actv-apps-uat\nspec:\n  podManagementPolicy: Parallel\n  replicas: 3\n  selector:\n    matchLabels:\n      app: zk\n  serviceName: zk-hs\n  template:\n    metadata:\n      labels:\n        app: zk\n    spec:\n      affinity:\n        podAntiAffinity:\n          requiredDuringSchedulingIgnoredDuringExecution:\n            - labelSelector:\n                matchExpressions:\n                  - key: app\n                    operator: In\n                    values:\n                      - zk\n              topologyKey: kubernetes.io/hostname\n      containers:\n        - command:\n            - sh\n            - -c\n            - start-zookeeper --servers=3 --data_dir=/var/lib/zookeeper/data --data_log_dir=/var/lib/zookeeper/data\n              --conf_dir=/opt/zookeeper/conf --client_port=2181 --election_port=3888 --server_port=2888\n              --tick_time=2000 --init_limit=10 --sync_limit=5 --heap=512M --max_client_cnxns=60\n              --snap_retain_count=3 --purge_interval=1 --max_session_timeout=40000 --min_session_timeout=4000\n              --log_level=ERROR\n          image: k8s.gcr.io/kubernetes-zookeeper:1.0-3.4.10\n          livenessProbe:\n            exec:\n              command:\n                - sh\n                - -c\n                - zookeeper-ready 2181\n            initialDelaySeconds: 10\n            timeoutSeconds: 5\n          name: kubernetes-zookeeper\n          ports:\n            - containerPort: 2181\n              name: client\n            - containerPort: 2888\n              name: server\n            - containerPort: 3888\n              name: leader-election\n          readinessProbe:\n            exec:\n              command:\n                - sh\n                - -c\n                - zookeeper-ready 2181\n            initialDelaySeconds: 10\n            timeoutSeconds: 5\n          resources:\n            requests:\n              cpu: \"0.5\"\n              memory: 1Gi\n          volumeMounts:\n            - mountPath: /var/lib/zookeeper\n              name: datadir\n      securityContext:\n        fsGroup: 1000\n        runAsUser: 1000\n      volumes:\n        - emptyDir: {}\n          name: datadir\n  updateStrategy:\n    type: RollingUpdate\n  volumeClaimTemplates:\n    - metadata:\n        annotations:\n          volume.beta.kubernetes.io/storage-class: standard\n          volume.beta.kubernetes.io/storage-provisioner: kubernetes.io/aws-ebs\n        name: datadir\n      spec:\n        accessModes:\n          - ReadWriteOnce\n        resources:\n          requests:\n            storage: 5Gi\n        storageClassName: standard\n---\napiVersion: policy/v1beta1\nkind: PodDisruptionBudget\nmetadata:\n  name: zk-pdb\n  namespace: actv-apps-uat\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      app: zk\n"
  },
  {
    "path": "testing/it_manifest_filter/pkg/testdata/statefulset2.expected.yaml",
    "content": "apiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  creationTimestamp: null\n  name: zk\n  namespace: actv-apps-uat\nspec:\n  podManagementPolicy: Parallel\n  replicas: 3\n  selector:\n    matchLabels:\n      app: zk\n  serviceName: zk-hs\n  template:\n    metadata:\n      creationTimestamp: null\n      labels:\n        app: zk\n    spec:\n      affinity:\n        podAntiAffinity:\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: app\n                operator: In\n                values:\n                - zk\n            topologyKey: kubernetes.io/hostname\n      containers:\n      - command:\n        - sh\n        - -c\n        - start-zookeeper --servers=3 --data_dir=/var/lib/zookeeper/data --data_log_dir=/var/lib/zookeeper/data\n          --conf_dir=/opt/zookeeper/conf --client_port=2181 --election_port=3888 --server_port=2888\n          --tick_time=2000 --init_limit=10 --sync_limit=5 --heap=512M --max_client_cnxns=60\n          --snap_retain_count=3 --purge_interval=1 --max_session_timeout=40000 --min_session_timeout=4000\n          --log_level=ERROR\n        image: k8s.gcr.io/kubernetes-zookeeper:1.0-3.4.10\n        livenessProbe:\n          exec:\n            command:\n            - sh\n            - -c\n            - zookeeper-ready 2181\n          initialDelaySeconds: 10\n          timeoutSeconds: 5\n        name: kubernetes-zookeeper\n        ports:\n        - containerPort: 2181\n          name: client\n        - containerPort: 2888\n          name: server\n        - containerPort: 3888\n          name: leader-election\n        readinessProbe:\n          exec:\n            command:\n            - sh\n            - -c\n            - zookeeper-ready 2181\n          initialDelaySeconds: 10\n          timeoutSeconds: 5\n        resources:\n          requests:\n            cpu: 500m\n            memory: 1Gi\n        volumeMounts:\n        - mountPath: /var/lib/zookeeper\n          name: datadir\n      securityContext:\n        fsGroup: 1000\n        runAsUser: 1000\n      volumes:\n      - emptyDir:\n          sizeLimit: 5Gi\n        name: datadir\n      - emptyDir: {}\n        name: datadir2\n  updateStrategy:\n    type: RollingUpdate\n---\napiVersion: policy/v1beta1\nkind: PodDisruptionBudget\nmetadata:\n  name: zk-pdb\n  namespace: actv-apps-uat\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      app: zk\n"
  },
  {
    "path": "testing/it_manifest_filter/pkg/testdata/statefulset2.yaml",
    "content": "apiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: zk\n  namespace: actv-apps-uat\nspec:\n  podManagementPolicy: Parallel\n  replicas: 3\n  selector:\n    matchLabels:\n      app: zk\n  serviceName: zk-hs\n  template:\n    metadata:\n      labels:\n        app: zk\n    spec:\n      affinity:\n        podAntiAffinity:\n          requiredDuringSchedulingIgnoredDuringExecution:\n            - labelSelector:\n                matchExpressions:\n                  - key: app\n                    operator: In\n                    values:\n                      - zk\n              topologyKey: kubernetes.io/hostname\n      containers:\n        - command:\n            - sh\n            - -c\n            - start-zookeeper --servers=3 --data_dir=/var/lib/zookeeper/data --data_log_dir=/var/lib/zookeeper/data\n              --conf_dir=/opt/zookeeper/conf --client_port=2181 --election_port=3888 --server_port=2888\n              --tick_time=2000 --init_limit=10 --sync_limit=5 --heap=512M --max_client_cnxns=60\n              --snap_retain_count=3 --purge_interval=1 --max_session_timeout=40000 --min_session_timeout=4000\n              --log_level=ERROR\n          image: k8s.gcr.io/kubernetes-zookeeper:1.0-3.4.10\n          livenessProbe:\n            exec:\n              command:\n                - sh\n                - -c\n                - zookeeper-ready 2181\n            initialDelaySeconds: 10\n            timeoutSeconds: 5\n          name: kubernetes-zookeeper\n          ports:\n            - containerPort: 2181\n              name: client\n            - containerPort: 2888\n              name: server\n            - containerPort: 3888\n              name: leader-election\n          readinessProbe:\n            exec:\n              command:\n                - sh\n                - -c\n                - zookeeper-ready 2181\n            initialDelaySeconds: 10\n            timeoutSeconds: 5\n          resources:\n            requests:\n              cpu: \"0.5\"\n              memory: 1Gi\n          volumeMounts:\n            - mountPath: /var/lib/zookeeper\n              name: datadir\n      securityContext:\n        fsGroup: 1000\n        runAsUser: 1000\n      volumes:\n        - emptyDir: {}\n          name: datadir\n  updateStrategy:\n    type: RollingUpdate\n  volumeClaimTemplates:\n    - metadata:\n        annotations:\n          volume.beta.kubernetes.io/storage-class: standard\n          volume.beta.kubernetes.io/storage-provisioner: kubernetes.io/aws-ebs\n        name: datadir\n      spec:\n        accessModes:\n          - ReadWriteOnce\n        resources:\n          requests:\n            storage: 5Gi\n        storageClassName: standard\n    - metadata:\n        annotations:\n          volume.beta.kubernetes.io/storage-class: standard\n          volume.beta.kubernetes.io/storage-provisioner: kubernetes.io/aws-ebs\n        name: datadir2\n      spec:\n        accessModes:\n          - ReadWriteOnce\n        storageClassName: standard\n\n---\napiVersion: policy/v1beta1\nkind: PodDisruptionBudget\nmetadata:\n  name: zk-pdb\n  namespace: actv-apps-uat\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      app: zk\n"
  },
  {
    "path": "testing/it_manifest_filter/pkg/testdata/statefulset3.expected.yaml",
    "content": "apiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  creationTimestamp: null\n  name: zk\n  namespace: actv-apps-uat\nspec:\n  podManagementPolicy: Parallel\n  replicas: 3\n  selector:\n    matchLabels:\n      app: zk\n  serviceName: zk-hs\n  template:\n    metadata:\n      creationTimestamp: null\n      labels:\n        app: zk\n    spec:\n      affinity:\n        podAntiAffinity:\n          requiredDuringSchedulingIgnoredDuringExecution:\n          - labelSelector:\n              matchExpressions:\n              - key: app\n                operator: In\n                values:\n                - zk\n            topologyKey: kubernetes.io/hostname\n      containers:\n      - command:\n        - sh\n        - -c\n        - start-zookeeper --servers=3 --data_dir=/var/lib/zookeeper/data --data_log_dir=/var/lib/zookeeper/data\n          --conf_dir=/opt/zookeeper/conf --client_port=2181 --election_port=3888 --server_port=2888\n          --tick_time=2000 --init_limit=10 --sync_limit=5 --heap=512M --max_client_cnxns=60\n          --snap_retain_count=3 --purge_interval=1 --max_session_timeout=40000 --min_session_timeout=4000\n          --log_level=ERROR\n        image: k8s.gcr.io/kubernetes-zookeeper:1.0-3.4.10\n        livenessProbe:\n          exec:\n            command:\n            - sh\n            - -c\n            - zookeeper-ready 2181\n          initialDelaySeconds: 10\n          timeoutSeconds: 5\n        name: kubernetes-zookeeper\n        ports:\n        - containerPort: 2181\n          name: client\n        - containerPort: 2888\n          name: server\n        - containerPort: 3888\n          name: leader-election\n        readinessProbe:\n          exec:\n            command:\n            - sh\n            - -c\n            - zookeeper-ready 2181\n          initialDelaySeconds: 10\n          timeoutSeconds: 5\n        resources:\n          requests:\n            cpu: 500m\n            memory: 1Gi\n        volumeMounts:\n        - mountPath: /var/lib/zookeeper\n          name: datadir\n      securityContext:\n        fsGroup: 1000\n        runAsUser: 1000\n      volumes:\n      - emptyDir:\n          sizeLimit: 5Gi\n        name: datadir\n  updateStrategy:\n    type: RollingUpdate\n---\napiVersion: policy/v1beta1\nkind: PodDisruptionBudget\nmetadata:\n  name: zk-pdb\n  namespace: actv-apps-uat\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      app: zk\n"
  },
  {
    "path": "testing/it_manifest_filter/pkg/testdata/statefulset3.yaml",
    "content": "apiVersion: apps/v1\nkind: StatefulSet\nmetadata:\n  name: zk\n  namespace: actv-apps-uat\nspec:\n  podManagementPolicy: Parallel\n  replicas: 3\n  selector:\n    matchLabels:\n      app: zk\n  serviceName: zk-hs\n  template:\n    metadata:\n      labels:\n        app: zk\n    spec:\n      affinity:\n        podAntiAffinity:\n          requiredDuringSchedulingIgnoredDuringExecution:\n            - labelSelector:\n                matchExpressions:\n                  - key: app\n                    operator: In\n                    values:\n                      - zk\n              topologyKey: kubernetes.io/hostname\n      containers:\n        - command:\n            - sh\n            - -c\n            - start-zookeeper --servers=3 --data_dir=/var/lib/zookeeper/data --data_log_dir=/var/lib/zookeeper/data\n              --conf_dir=/opt/zookeeper/conf --client_port=2181 --election_port=3888 --server_port=2888\n              --tick_time=2000 --init_limit=10 --sync_limit=5 --heap=512M --max_client_cnxns=60\n              --snap_retain_count=3 --purge_interval=1 --max_session_timeout=40000 --min_session_timeout=4000\n              --log_level=ERROR\n          image: k8s.gcr.io/kubernetes-zookeeper:1.0-3.4.10\n          livenessProbe:\n            exec:\n              command:\n                - sh\n                - -c\n                - zookeeper-ready 2181\n            initialDelaySeconds: 10\n            timeoutSeconds: 5\n          name: kubernetes-zookeeper\n          ports:\n            - containerPort: 2181\n              name: client\n            - containerPort: 2888\n              name: server\n            - containerPort: 3888\n              name: leader-election\n          readinessProbe:\n            exec:\n              command:\n                - sh\n                - -c\n                - zookeeper-ready 2181\n            initialDelaySeconds: 10\n            timeoutSeconds: 5\n          resources:\n            requests:\n              cpu: \"0.5\"\n              memory: 1Gi\n          volumeMounts:\n            - mountPath: /var/lib/zookeeper\n              name: datadir\n      securityContext:\n        fsGroup: 1000\n        runAsUser: 1000\n  updateStrategy:\n    type: RollingUpdate\n  volumeClaimTemplates:\n    - metadata:\n        annotations:\n          volume.beta.kubernetes.io/storage-class: standard\n          volume.beta.kubernetes.io/storage-provisioner: kubernetes.io/aws-ebs\n        name: datadir\n      spec:\n        accessModes:\n          - ReadWriteOnce\n        resources:\n          requests:\n            storage: 5Gi\n        storageClassName: standard\n---\napiVersion: policy/v1beta1\nkind: PodDisruptionBudget\nmetadata:\n  name: zk-pdb\n  namespace: actv-apps-uat\nspec:\n  maxUnavailable: 1\n  selector:\n    matchLabels:\n      app: zk\n"
  },
  {
    "path": "testing/it_sidecar/BUILD.bazel",
    "content": "load(\"@rules_go//go:def.bzl\", \"go_binary\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"it_sidecar.go\"],\n    importpath = \"github.com/adobe/rules_gitops/testing/it_sidecar\",\n    visibility = [\"//visibility:private\"],\n    deps = [\n        \"//testing/it_sidecar/stern:go_default_library\",\n        \"@io_k8s_api//core/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_client_go//informers:go_default_library\",\n        \"@io_k8s_client_go//kubernetes:go_default_library\",\n        \"@io_k8s_client_go//plugin/pkg/client/auth/gcp:go_default_library\",\n        \"@io_k8s_client_go//rest:go_default_library\",\n        \"@io_k8s_client_go//tools/cache:go_default_library\",\n        \"@io_k8s_client_go//tools/clientcmd:go_default_library\",\n        \"@io_k8s_client_go//tools/portforward:go_default_library\",\n        \"@io_k8s_client_go//transport/spdy:go_default_library\",\n        \"@io_k8s_client_go//util/homedir:go_default_library\",\n    ],\n)\n\ngo_binary(\n    name = \"it_sidecar\",\n    embed = [\":go_default_library\"],\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "testing/it_sidecar/client/BUILD.bazel",
    "content": "load(\"@rules_go//go:def.bzl\", \"go_library\")\n\nexports_files([\"noop.setup\"])\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"sidecar_client.go\"],\n    data = [\n        \":noop.setup\",\n    ],\n    importpath = \"github.com/adobe/rules_gitops/testing/it_sidecar/client\",\n    visibility = [\"//visibility:public\"],\n)\n"
  },
  {
    "path": "testing/it_sidecar/client/noop.setup",
    "content": "#! /bin/bash\n\n# This is a no-op setup script that simulates the K8Setup script output\necho \"FORWARD foo:8000:8000\"\necho \"FORWARD bar:8001:8001\"\n\necho \"READY\"\n"
  },
  {
    "path": "testing/it_sidecar/client/sidecar_client.go",
    "content": "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)\n\n// K8STestSetup is instantiated given the pods and services we must wait for\ntype K8STestSetup struct {\n\tWaitForPods         []string\n\tPortForwardServices map[string]int\n\n\tforwards map[string]int\n\n\tcmd *exec.Cmd\n\n\tin  io.WriteCloser\n\tout io.ReadCloser\n\ter  io.ReadCloser\n\n\t// ReadyCallback for custom setup after services are ready and before pre-test\n\tReadyCallback Callback\n}\n\n// Callback function type is invoked post-setup but pre-test\ntype Callback func() error\n\nvar setupCMD = flag.String(\"setup\", \"\", \"the path to the it setup command\")\n\n// TestMain will execute the provided setup command, wait for configured pods and services to be\n// ready, and then forwards service logs to test output.  On completion, signals to the it_sidecar\n// to teardown the test namespace\nfunc (s *K8STestSetup) TestMain(m *testing.M) {\n\ts.forwards = make(map[string]int)\n\twg := new(sync.WaitGroup)\n\twg.Add(2) // there will be 2 goroutines, one reading stdout and one reading stdin\n\tos.Exit(func() int {\n\t\tflag.Parse()\n\t\t// Defer sidecar process tear-down.\n\t\tdefer func() {\n\t\t\t//Closing standard-in pipe signals sidecar process to exit,\n\t\t\ts.in.Close()\n\t\t\twg.Wait() // Wait for reader goroutines to actually finish\n\t\t\tif err := s.cmd.Wait(); err != nil {\n\t\t\t\tlog.Fatal(err)\n\t\t\t}\n\t\t}()\n\t\ts.before(wg)\n\t\tif s.ReadyCallback != nil {\n\t\t\terr := s.ReadyCallback()\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatal(err)\n\t\t\t}\n\t\t}\n\n\t\t// Run tests.\n\t\treturn m.Run()\n\t}())\n}\n\nfunc (s *K8STestSetup) GetServiceLocalPort(serviceName string) int {\n\treturn s.forwards[serviceName]\n}\n\nfunc (s *K8STestSetup) before(wg *sync.WaitGroup) {\n\tlog.Printf(\"setup command: %s\\n\", *setupCMD)\n\n\targs := make([]string, 0)\n\tfor _, app := range s.WaitForPods {\n\t\targs = append(args, fmt.Sprintf(\"-waitforapp=%s\", app))\n\t}\n\tfor service, port := range s.PortForwardServices {\n\t\targs = append(args, fmt.Sprintf(\"-portforward=%s:%d\", service, port))\n\t}\n\n\ts.cmd = exec.Command(*setupCMD, args...)\n\n\tvar err error\n\t// Open and start reading stderr in a new goroutine. StderrPipe will be closed automatically by the call to Wait\n\t// so we do not need to close this ourselves.  We must also guarantee that all reads on this pipe are completed\n\t// before calling wait, so the goroutines below must be canceled before the defered teardown above\n\tif s.er, err = s.cmd.StderrPipe(); err != nil {\n\t\tlog.Fatal(fmt.Errorf(\"unable to read  setup command STDOUT; %w\", err))\n\t}\n\n\n\tgo func() {\n\t\trd := bufio.NewReader(s.er)\n\t\tfor {\n\t\t\tstr, err := rd.ReadString('\\n')\n\t\t\tif err == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatal(err)\n\t\t\t}\n\t\t\tlog.Print(str)\n\t\t}\n\t\twg.Done()\n\t}()\n\n\t//Open stdin and stdout\n\tif s.out, err = s.cmd.StdoutPipe(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tif s.in, err = s.cmd.StdinPipe(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t//Start the sidecar process\n\tif err := s.cmd.Start(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t//Wait for all pods to be ready\n\trd := bufio.NewReader(s.out)\nwaitForReady:\n\tfor {\n\t\tstr, err := rd.ReadString('\\n')\n\t\tif err != nil {\n\t\t\tlog.Fatal(\"Unable to read from setup script stdout. Cannot wait for pods\")\n\t\t}\n\t\tfmt.Print(str)\n\t\tif strings.HasPrefix(str, \"FORWARD\") {\n\t\t\t// remove the \"FORWARD \" prefix, and any trailing space, split on \":\"\n\t\t\tparts := strings.Split(strings.TrimSpace(str[8:]), \":\")\n\t\t\tlocalPort, _ := strconv.Atoi(parts[2])\n\t\t\ts.forwards[parts[0]] = localPort\n\t\t}\n\t\tif \"READY\\n\" == str {\n\t\t\tbreak waitForReady\n\t\t}\n\t}\n\t//Start reading stdout in a new goroutine\n\tgo func() {\n\t\tfor {\n\t\t\tstr, err := rd.ReadString('\\n')\n\t\t\tif err == io.EOF {\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatal(err)\n\t\t\t}\n\t\t\tlog.Print(str)\n\t\t}\n\t\twg.Done()\n\t}()\n\n}\n"
  },
  {
    "path": "testing/it_sidecar/client/test_callback/BUILD.bazel",
    "content": "load(\"@rules_go//go:def.bzl\", \"go_test\")\n\ngo_test(\n    name = \"go_default_test\",\n    srcs = [\"sidecar_client_test.go\"],\n    args = [\n        \"-setup\",\n        \"$(location //testing/it_sidecar/client:noop.setup)\",\n    ],\n    data = [\n        \"//testing/it_sidecar/client:noop.setup\",\n    ],\n    rundir = \".\",\n    deps = [\"//testing/it_sidecar/client:go_default_library\"],\n)\n"
  },
  {
    "path": "testing/it_sidecar/client/test_callback/sidecar_client_test.go",
    "content": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\npackage test_callback\n\nimport (\n\t\"github.com/adobe/rules_gitops/testing/it_sidecar/client\"\n\t\"testing\"\n)\n\nvar (\n\tsetup         client.K8STestSetup\n\tisCallbackRun bool\n)\n\nfunc TestMain(m *testing.M) {\n\tcallback := func() error {\n\t\tisCallbackRun = true\n\t\treturn nil\n\t}\n\n\tsetup := client.K8STestSetup{\n\t\tPortForwardServices: map[string]int{},\n\t\tReadyCallback:       callback,\n\t}\n\n\tsetup.TestMain(m)\n}\n\n// TestReadyCallback validates that the pre-test ReadyCallback is run. Note that this test scenario assumes\n// that a K8STestSetup in TestMain will invoke the test.\nfunc TestReadyCallback(t *testing.T) {\n\tif !isCallbackRun {\n\t\tt.Fatalf(\"ready callback should have been run\")\n\t}\n}\n"
  },
  {
    "path": "testing/it_sidecar/client/test_no_callback/BUILD.bazel",
    "content": "load(\"@rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\"sidecar_client_test_no_callback.go\"],\n    importpath = \"github.com/adobe/rules_gitops/testing/it_sidecar/client/test_no_callback\",\n    visibility = [\"//visibility:public\"],\n    deps = [\"//testing/it_sidecar/client:go_default_library\"],\n)\n"
  },
  {
    "path": "testing/it_sidecar/client/test_no_callback/sidecar_client_test_no_callback.go",
    "content": "/*\nCopyright 2020 Adobe. All rights reserved.\nThis file is licensed to you under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License. You may obtain a copy\nof the License at http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under\nthe License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\nOF ANY KIND, either express or implied. See the License for the specific language\ngoverning permissions and limitations under the License.\n*/\npackage test_no_callback\n\nimport (\n\t\"github.com/adobe/rules_gitops/testing/it_sidecar/client\"\n\t\"testing\"\n)\n\nvar (\n\tsetup         client.K8STestSetup\n)\n\n// The setup should run without error since ReadyCallback is optional\nfunc TestMain(m *testing.M) {\n\tsetup := client.K8STestSetup{\n\t\tPortForwardServices: map[string]int{},\n\t}\n\n\tsetup.TestMain(m)\n}\n"
  },
  {
    "path": "testing/it_sidecar/it_sidecar.go",
    "content": "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/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/adobe/rules_gitops/testing/it_sidecar/stern\"\n\n\tv1 \"k8s.io/api/core/v1\"\n\tmeta_v1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/informers\"\n\t\"k8s.io/client-go/kubernetes\"\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth/gcp\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/tools/cache\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\t\"k8s.io/client-go/tools/portforward\"\n\t\"k8s.io/client-go/transport/spdy\"\n\t\"k8s.io/client-go/util/homedir\"\n)\n\ntype portForwardConf struct {\n\tservices map[string][]uint16\n}\n\nfunc (i *portForwardConf) String() string {\n\treturn fmt.Sprintf(\"%v\", i.services)\n}\n\nfunc (i *portForwardConf) Set(value string) error {\n\tv := strings.SplitN(value, \":\", 2)\n\tif len(v) != 2 {\n\t\treturn fmt.Errorf(\"incorrect portforward '%s': must be in form of service:port\", value)\n\t}\n\tport, err := strconv.ParseUint(v[1], 10, 16)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"incorrect port in portforward '%s': %v\", value, err)\n\t}\n\ti.services[v[0]] = append(i.services[v[0]], uint16(port))\n\treturn nil\n}\n\ntype arrayFlags []string\n\nfunc (i *arrayFlags) String() string {\n\treturn \"my string representation\"\n}\n\nfunc (i *arrayFlags) Set(value string) error {\n\t*i = append(*i, value)\n\treturn nil\n}\n\nvar (\n\tnamespace       = flag.String(\"namespace\", os.Getenv(\"NAMESPACE\"), \"kubernetes namespace\")\n\ttimeout         = flag.Duration(\"timeout\", time.Second*30, \"execution timeout\")\n\tdeleteNamespace = flag.Bool(\"delete_namespace\", false, \"delete namespace as part of the cleanup\")\n\tpfconfig        = portForwardConf{services: make(map[string][]uint16)}\n\tsignalChannel   chan os.Signal\n\tkubeconfig      string\n\twaitForApps     arrayFlags\n)\n\nfunc init() {\n\tflag.Var(&pfconfig, \"portforward\", \"set a port forward item in form of servicename:port\")\n\tflag.StringVar(&kubeconfig, \"kubeconfig\", os.Getenv(\"KUBECONFIG\"), \"path to kubernetes config file\")\n\tflag.Var(&waitForApps, \"waitforapp\", \"wait for pods with label app=<this parameter>\")\n}\n\n// contains returns true if slice v contains an item\nfunc contains(v []string, item string) bool {\n\tfor _, s := range v {\n\t\tif s == item {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// listReadyApps converts a list returned from podsInformer.GetStore().List() to a map containing apps with ready status\n// app is determined by app label\nfunc listReadyApps(list []interface{}) (readypods, notReady []string) {\n\tvar readyApps []string\n\tfor _, it := range list {\n\t\tpod, ok := it.(*v1.Pod)\n\t\tif !ok {\n\t\t\tpanic(errors.New(\"expected pod in informer\"))\n\t\t}\n\t\tfor _, cond := range pod.Status.Conditions {\n\t\t\tif cond.Type == v1.PodReady {\n\t\t\t\tif cond.Status == v1.ConditionTrue {\n\t\t\t\t\treadypods = append(readypods, pod.Name)\n\t\t\t\t\tapp := pod.GetLabels()[\"app\"]\n\t\t\t\t\tif app != \"\" {\n\t\t\t\t\t\treadyApps = append(readyApps, app)\n\t\t\t\t\t}\n\t\t\t\t\tapp = pod.GetLabels()[\"app.kubernetes.io/name\"]\n\t\t\t\t\tif app != \"\" {\n\t\t\t\t\t\treadyApps = append(readyApps, app)\n\t\t\t\t\t}\n\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tfor _, app := range waitForApps {\n\t\tif !contains(readyApps, app) {\n\t\t\tnotReady = append(notReady, app)\n\t\t}\n\t}\n\treturn\n}\n\nfunc waitForPods(ctx context.Context, clientset *kubernetes.Clientset) error {\n\tctx, cancel := context.WithCancel(ctx)\n\tdefer cancel()\n\tevents := make(chan interface{})\n\tfn := func(obj interface{}) {\n\t\tevents <- obj\n\t}\n\n\thandler := &cache.ResourceEventHandlerFuncs{\n\t\tAddFunc:    fn,\n\t\tDeleteFunc: fn,\n\t\tUpdateFunc: func(old interface{}, new interface{}) {\n\t\t\tfn(new)\n\t\t},\n\t}\n\n\tkubeInformerFactory := informers.NewFilteredSharedInformerFactory(clientset, time.Second*30, *namespace, nil)\n\tpodsInformer := kubeInformerFactory.Core().V1().Pods().Informer()\n\tpodsInformer.AddEventHandler(handler)\n\tgo kubeInformerFactory.Start(ctx.Done())\n\nwaitForPodsUp:\n\tfor {\n\t\tselect {\n\t\tcase <-events:\n\t\t\tv := podsInformer.GetStore().List()\n\t\t\tready, notReady := listReadyApps(v)\n\t\t\tlog.Print(\"ready pods:\", ready)\n\t\t\tif len(notReady) != 0 {\n\t\t\t\tlog.Print(\"waiting for apps:\", notReady)\n\t\t\t} else {\n\t\t\t\tlog.Println(\"all apps are ready\")\n\t\t\t\tbreak waitForPodsUp\n\t\t\t}\n\t\tcase <-ctx.Done():\n\t\t\treturn errors.New(\"timed out waiting for apps\")\n\t\t}\n\t}\n\treturn nil\n}\n\n// listReadyServices converts a list returned from endpointsInformer.GetStore().List() to a list of services with ready status\nfunc listReadyServices(list []interface{}) (ready, notReady []string) {\n\tfor _, it := range list {\n\t\tep, ok := it.(*v1.Endpoints)\n\t\tif !ok {\n\t\t\tpanic(errors.New(\"expected EndpointsList in informer\"))\n\t\t}\n\t\tfor _, subset := range ep.Subsets {\n\t\t\tif len(subset.Addresses) > 0 {\n\t\t\t\tready = append(ready, ep.Name)\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}\n\tfor service, _ := range pfconfig.services {\n\t\tif !contains(ready, service) {\n\t\t\tnotReady = append(notReady, service)\n\t\t}\n\t}\n\treturn\n}\n\nfunc waitForEndpoints(ctx context.Context, clientset *kubernetes.Clientset, config *rest.Config) error {\n\tevents := make(chan interface{})\n\tfn := func(obj interface{}) {\n\t\tevents <- obj\n\t}\n\n\thandler := &cache.ResourceEventHandlerFuncs{\n\t\tAddFunc:    fn,\n\t\tDeleteFunc: fn,\n\t\tUpdateFunc: func(old interface{}, new interface{}) {\n\t\t\tfn(new)\n\t\t},\n\t}\n\n\tkubeInformerFactory := informers.NewFilteredSharedInformerFactory(clientset, time.Second*30, *namespace, nil)\n\tendpointsInformer := kubeInformerFactory.Core().V1().Endpoints().Informer()\n\tendpointsInformer.AddEventHandler(handler)\n\tgo kubeInformerFactory.Start(ctx.Done())\n\n\tallReadyServices := make(map[string]bool)\nwaitForServicesUp:\n\tfor {\n\t\tselect {\n\t\tcase <-events:\n\t\t\tv := endpointsInformer.GetStore().List()\n\t\t\tready, notReady := listReadyServices(v)\n\t\t\tlog.Print(\"ready services:\", ready)\n\t\t\tfor _, svc := range ready {\n\t\t\t\tif !allReadyServices[svc] {\n\t\t\t\t\tallReadyServices[svc] = true\n\t\t\t\t\tlog.Print(\"SERVICE_READY \", svc)\n\t\t\t\t\tif ports := pfconfig.services[svc]; len(ports) > 0 {\n\t\t\t\t\t\terr := portForward(ctx, clientset, config, svc, ports)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn err\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif len(notReady) != 0 {\n\t\t\t\tlog.Print(\"waiting for endpoints:\", notReady)\n\t\t\t} else {\n\t\t\t\tlog.Println(\"all services are ready\")\n\t\t\t\tbreak waitForServicesUp\n\t\t\t}\n\t\tcase <-ctx.Done():\n\t\t\treturn errors.New(\"timed out waiting for services\")\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc portForward(ctx context.Context, clientset *kubernetes.Clientset, config *rest.Config, serviceName string, ports []uint16) error {\n\t// port forward\n\tvar wg sync.WaitGroup\n\twg.Add(len(ports))\n\tfor _, port := range ports {\n\t\tep, err := clientset.CoreV1().Endpoints(*namespace).Get(ctx, serviceName, meta_v1.GetOptions{})\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error listing endpoints for service %s: %v\", serviceName, err)\n\t\t}\n\t\tvar podnamespace, podname string\n\t\tfor _, subset := range ep.Subsets {\n\t\t\tif len(subset.Addresses) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tpodnamespace = subset.Addresses[0].TargetRef.Namespace\n\t\t\tpodname = subset.Addresses[0].TargetRef.Name\n\t\t\tbreak\n\t\t}\n\t\tif podnamespace == \"\" || podname == \"\" {\n\t\t\treturn fmt.Errorf(\"no pods are available for service %s\", serviceName)\n\t\t}\n\t\tlog.Printf(\"%s -> %s/%s\", serviceName, podnamespace, podname)\n\n\t\turl := clientset.CoreV1().RESTClient().Post().Resource(\"pods\").Namespace(podnamespace).Name(podname).SubResource(\"portforward\").URL()\n\t\ttransport, upgrader, err := spdy.RoundTripperFor(config)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Could not create round tripper: %v\", err)\n\t\t}\n\t\tdialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, \"POST\", url)\n\t\tports := []string{fmt.Sprintf(\":%d\", port)}\n\t\treadyChan := make(chan struct{}, 1)\n\t\tpf, err := portforward.New(dialer, ports, ctx.Done(), readyChan, os.Stderr, os.Stderr)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"Could not port forward into pod: %v\", err)\n\t\t}\n\t\tgo func(port uint16) {\n\t\t\terr := pf.ForwardPorts()\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatalf(\"Could not forward ports for %s:%d : %v\", serviceName, port, err)\n\t\t\t}\n\t\t}(port)\n\t\tgo func(port uint16) {\n\t\t\t<-pf.Ready\n\t\t\tports, err := pf.GetPorts()\n\t\t\tif err != nil {\n\t\t\t\tlog.Fatalf(\"Could not get forwarded ports for %s:%d : %v\", serviceName, port, err)\n\t\t\t}\n\t\t\tfor _, port := range ports {\n\t\t\t\tfmt.Printf(\"FORWARD %s:%d:%d\\n\", serviceName, port.Remote, port.Local)\n\t\t\t}\n\t\t\twg.Done()\n\t\t}(port)\n\t}\n\twg.Wait()\n\treturn nil\n}\n\nfunc cleanup(clientset *kubernetes.Clientset) {\n\tlog.Print(\"Cleanup\")\n\tif *deleteNamespace && *namespace != \"\" {\n\t\tlog.Printf(\"deleting namespace %s\", *namespace)\n\t\ts := meta_v1.DeletePropagationBackground\n\t\tif err := clientset.CoreV1().Namespaces().Delete(context.Background(), *namespace, meta_v1.DeleteOptions{PropagationPolicy: &s}); err != nil {\n\t\t\tlog.Printf(\"Unable to delete namespace %s: %v\", *namespace, err)\n\t\t}\n\t}\n}\n\nfunc main() {\n\tflag.Parse()\n\tlog.SetOutput(os.Stdout)\n\n\tctx, cancel := context.WithTimeout(context.Background(), *timeout)\n\n\tsignalChannel = make(chan os.Signal, 1)\n\tsignal.Notify(signalChannel, os.Interrupt, syscall.SIGTERM)\n\tdefer func() {\n\t\tsignal.Stop(signalChannel)\n\t\tcancel()\n\t}()\n\t// cancel context if signal is received\n\tgo func() {\n\t\tselect {\n\t\tcase <-signalChannel:\n\t\t\tcancel()\n\t\tcase <-ctx.Done():\n\t\t}\n\t}()\n\t// cancel context if stdin is closed\n\tgo func() {\n\t\treader := bufio.NewReader(os.Stdin)\n\t\tfor {\n\t\t\t_, _, err := reader.ReadRune()\n\t\t\tif err != nil && err == io.EOF {\n\t\t\t\tcancel()\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t}()\n\n\tvar clientset *kubernetes.Clientset\n\tif kubeconfig == \"\" {\n\t\t_, ok := os.LookupEnv(\"KUBERNETES_SERVICE_HOST\")\n\t\tif !ok {\n\t\t\tkubeconfig = filepath.Join(homedir.HomeDir(), \".kube\", \"config\")\n\t\t}\n\t}\n\tconfig, err := clientcmd.BuildConfigFromFlags(\"\", kubeconfig)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tclientset = kubernetes.NewForConfigOrDie(config)\n\tdefer cleanup(clientset)\n\n\tgo stern.Run(ctx, *namespace, clientset)\n\n\tif len(waitForApps) > 0 {\n\t\terr = waitForPods(ctx, clientset)\n\t\tif err != nil {\n\t\t\tlog.Print(err)\n\t\t\treturn\n\t\t}\n\t}\n\tif len(pfconfig.services) > 0 {\n\t\terr = waitForEndpoints(ctx, clientset, config)\n\t\tif err != nil {\n\t\t\tlog.Print(err)\n\t\t\treturn\n\t\t}\n\t}\n\n\tfmt.Println(\"READY\")\n\t<-ctx.Done()\n}\n"
  },
  {
    "path": "testing/it_sidecar/stern/BUILD.bazel",
    "content": "load(\"@rules_go//go:def.bzl\", \"go_library\")\n\ngo_library(\n    name = \"go_default_library\",\n    srcs = [\n        \"container_state.go\",\n        \"main.go\",\n        \"tail.go\",\n        \"watch.go\",\n    ],\n    importpath = \"github.com/adobe/rules_gitops/testing/it_sidecar/stern\",\n    visibility = [\"//visibility:public\"],\n    deps = [\n        \"@io_k8s_api//core/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/labels:go_default_library\",\n        \"@io_k8s_apimachinery//pkg/watch:go_default_library\",\n        \"@io_k8s_client_go//kubernetes:go_default_library\",\n        \"@io_k8s_client_go//kubernetes/typed/core/v1:go_default_library\",\n        \"@io_k8s_client_go//rest:go_default_library\",\n    ],\n)\n"
  },
  {
    "path": "testing/it_sidecar/stern/container_state.go",
    "content": "//   Copyright 2016 Wercker Holding BV\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n//   you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n//       http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n//   distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n//   limitations under the License.\n\npackage stern\n\nimport (\n\t\"errors\"\n\n\t\"k8s.io/api/core/v1\"\n)\n\ntype ContainerState string\n\nconst (\n\tRUNNING    = \"running\"\n\tWAITING    = \"waiting\"\n\tTERMINATED = \"terminated\"\n)\n\nfunc NewContainerState(stateConfig string) (ContainerState, error) {\n\tif stateConfig == RUNNING {\n\t\treturn RUNNING, nil\n\t} else if stateConfig == WAITING {\n\t\treturn WAITING, nil\n\t} else if stateConfig == TERMINATED {\n\t\treturn TERMINATED, nil\n\t}\n\n\treturn \"\", errors.New(\"containerState should be one of 'running', 'waiting', or 'terminated'\")\n}\n\nfunc (stateConfig ContainerState) Match(containerState v1.ContainerState) bool {\n\treturn (stateConfig == RUNNING && containerState.Running != nil) ||\n\t\t(stateConfig == WAITING && containerState.Waiting != nil) ||\n\t\t(stateConfig == TERMINATED && containerState.Terminated != nil)\n}\n"
  },
  {
    "path": "testing/it_sidecar/stern/main.go",
    "content": "//   Copyright 2016 Wercker Holding BV\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n//   you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n//       http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n//   distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n//   limitations under the License.\n\npackage stern\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"regexp\"\n\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/client-go/kubernetes\"\n)\n\n// Run starts the main run loop\nfunc Run(ctx context.Context, namespace string, clientset *kubernetes.Clientset) error {\n\n\tadded, removed, err := Watch(ctx, clientset.CoreV1().Pods(namespace), regexp.MustCompile(\".*\"), regexp.MustCompile(\".*\"), RUNNING, labels.Everything())\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to set up watch: %v\", err)\n\t}\n\n\ttails := make(map[string]*Tail)\n\n\tgo func() {\n\t\tfor p := range added {\n\t\t\tid := p.GetID()\n\t\t\tif tails[id] != nil {\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttail := NewTail(p.Namespace, p.Pod, p.Container)\n\t\t\ttails[id] = tail\n\n\t\t\ttail.Start(ctx, clientset.CoreV1().Pods(p.Namespace))\n\t\t}\n\t}()\n\n\tgo func() {\n\t\tfor p := range removed {\n\t\t\tid := p.GetID()\n\t\t\tif tails[id] == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ttails[id].Close()\n\t\t\tdelete(tails, id)\n\t\t}\n\t}()\n\n\t<-ctx.Done()\n\n\treturn nil\n}\n"
  },
  {
    "path": "testing/it_sidecar/stern/tail.go",
    "content": "//   Copyright 2016 Wercker Holding BV\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n//   you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n//       http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n//   distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n//   limitations under the License.\n\npackage stern\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\tv1 \"k8s.io/client-go/kubernetes/typed/core/v1\"\n\t\"k8s.io/client-go/rest\"\n)\n\ntype Tail struct {\n\tNamespace     string\n\tPodName       string\n\tContainerName string\n\treq           *rest.Request\n\tclosed        chan struct{}\n}\n\n// NewTail returns a new tail for a Kubernetes container inside a pod\nfunc NewTail(namespace, podName, containerName string) *Tail {\n\treturn &Tail{\n\t\tNamespace:     namespace,\n\t\tPodName:       podName,\n\t\tContainerName: containerName,\n\t\tclosed:        make(chan struct{}),\n\t}\n}\n\n// Start starts tailing\nfunc (t *Tail) Start(ctx context.Context, i v1.PodInterface) {\n\n\tgo func() {\n\t\tfmt.Fprintf(os.Stderr, \"+ %s/%s\\n\", t.PodName, t.ContainerName)\n\n\t\treq := i.GetLogs(t.PodName, &corev1.PodLogOptions{\n\t\t\tFollow:     true,\n\t\t\tTimestamps: true,\n\t\t\tContainer:  t.ContainerName,\n\t\t})\n\n\t\tstream, err := req.Stream(ctx)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"Error opening stream to %s/%s/%s: %s\", t.Namespace, t.PodName, t.ContainerName, err)\n\t\t\treturn\n\t\t}\n\t\tdefer stream.Close()\n\n\t\tgo func() {\n\t\t\t<-t.closed\n\t\t\tstream.Close()\n\t\t}()\n\n\t\treader := bufio.NewReader(stream)\n\n\t\tfor {\n\t\t\tline, err := reader.ReadBytes('\\n')\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tstr := string(line)\n\t\t\tt.Print(str)\n\t\t}\n\t}()\n\n\tgo func() {\n\t\t<-ctx.Done()\n\t\tclose(t.closed)\n\t}()\n}\n\n// Close stops tailing\nfunc (t *Tail) Close() {\n\tfmt.Fprintf(os.Stderr, \"Log finished %s\\n\", t.PodName)\n\tclose(t.closed)\n}\n\n// Print prints a color coded log message with the pod and container names\nfunc (t *Tail) Print(msg string) {\n\tfmt.Fprintf(os.Stderr, \"[%s/%s]: %s\", t.PodName, t.ContainerName, msg)\n}\n"
  },
  {
    "path": "testing/it_sidecar/stern/watch.go",
    "content": "//   Copyright 2016 Wercker Holding BV\n//\n//   Licensed under the Apache License, Version 2.0 (the \"License\");\n//   you may not use this file except in compliance with the License.\n//   You may obtain a copy of the License at\n//\n//       http://www.apache.org/licenses/LICENSE-2.0\n//\n//   Unless required by applicable law or agreed to in writing, software\n//   distributed under the License is distributed on an \"AS IS\" BASIS,\n//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n//   See the License for the specific language governing permissions and\n//   limitations under the License.\n\npackage stern\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"regexp\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/apimachinery/pkg/watch\"\n\t\"k8s.io/client-go/kubernetes/typed/core/v1\"\n)\n\n// Target is a target to watch\ntype Target struct {\n\tNamespace string\n\tPod       string\n\tContainer string\n}\n\n// GetID returns the ID of the object\nfunc (t *Target) GetID() string {\n\treturn fmt.Sprintf(\"%s-%s-%s\", t.Namespace, t.Pod, t.Container)\n}\n\n// Watch starts listening to Kubernetes events and emits modified\n// containers/pods. The first result is targets added, the second is targets\n// removed\nfunc Watch(ctx context.Context, i v1.PodInterface, podFilter *regexp.Regexp, containerFilter *regexp.Regexp, containerState ContainerState, labelSelector labels.Selector) (chan *Target, chan *Target, error) {\n\twatcher, err := i.Watch(ctx, metav1.ListOptions{Watch: true, LabelSelector: labelSelector.String()})\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"failed to set up watch: %s\", err)\n\t}\n\n\tadded := make(chan *Target)\n\tremoved := make(chan *Target)\n\n\tgo func() {\n\t\tfor {\n\t\t\tselect {\n\t\t\tcase e := <-watcher.ResultChan():\n\t\t\t\tif e.Object == nil {\n\t\t\t\t\t// Closed because of error\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tvar (\n\t\t\t\t\tpod *corev1.Pod\n\t\t\t\t\tok  bool\n\t\t\t\t)\n\t\t\t\tif pod, ok = e.Object.(*corev1.Pod); !ok {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tif !podFilter.MatchString(pod.Name) {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\n\t\t\t\tswitch e.Type {\n\t\t\t\tcase watch.Added, watch.Modified:\n\t\t\t\t\tvar statuses []corev1.ContainerStatus\n\t\t\t\t\tstatuses = append(statuses, pod.Status.InitContainerStatuses...)\n\t\t\t\t\tstatuses = append(statuses, pod.Status.ContainerStatuses...)\n\n\t\t\t\t\tfor _, c := range statuses {\n\t\t\t\t\t\tif !containerFilter.MatchString(c.Name) {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// if containerExcludeFilter != nil && containerExcludeFilter.MatchString(c.Name) {\n\t\t\t\t\t\t// \tcontinue\n\t\t\t\t\t\t// }\n\n\t\t\t\t\t\tif containerState.Match(c.State) {\n\t\t\t\t\t\t\tadded <- &Target{\n\t\t\t\t\t\t\t\tNamespace: pod.Namespace,\n\t\t\t\t\t\t\t\tPod:       pod.Name,\n\t\t\t\t\t\t\t\tContainer: c.Name,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\tcase watch.Deleted:\n\t\t\t\t\tvar containers []corev1.Container\n\t\t\t\t\tcontainers = append(containers, pod.Spec.Containers...)\n\t\t\t\t\tcontainers = append(containers, pod.Spec.InitContainers...)\n\n\t\t\t\t\tfor _, c := range containers {\n\t\t\t\t\t\tif !containerFilter.MatchString(c.Name) {\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// if containerExcludeFilter != nil && containerExcludeFilter.MatchString(c.Name) {\n\t\t\t\t\t\t// \tcontinue\n\t\t\t\t\t\t// }\n\n\t\t\t\t\t\tremoved <- &Target{\n\t\t\t\t\t\t\tNamespace: pod.Namespace,\n\t\t\t\t\t\t\tPod:       pod.Name,\n\t\t\t\t\t\t\tContainer: c.Name,\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tcase <-ctx.Done():\n\t\t\t\twatcher.Stop()\n\t\t\t\tclose(added)\n\t\t\t\tclose(removed)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn added, removed, nil\n}\n"
  },
  {
    "path": "testing/private/BUILD.bazel",
    "content": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\nload(\"@rules_shell//shell:sh_binary.bzl\", \"sh_binary\")\nload(\"//kustomize:defs.bzl\", \"kustomize_binary\")\n\nexports_files([\n    \"k8s_test_namespace.sh.tpl\",\n])\n\nkustomize_binary(\n    name = \"kustomize_bin\",\n)\n\nsh_binary(\n    name = \"set_namespace\",\n    srcs = [\"set_namespace.sh\"],\n    data = [\":kustomize_bin\"],\n    visibility = [\n        \"//testing/private:__subpackages__\",\n        \"//tests:__subpackages__\",\n    ],\n    deps = [\"@bazel_tools//tools/bash/runfiles\"],\n)\n\nbzl_library(\n    name = \"k8s_test_setup\",\n    srcs = [\"k8s_test_setup.bzl\"],\n    visibility = [\"//testing:__subpackages__\"],\n    deps = [\n        \"//adapters:providers\",\n        \"//kustomize:defs\",\n    ],\n)\n\nbzl_library(\n    name = \"k8s_test_namespace\",\n    srcs = [\"k8s_test_namespace.bzl\"],\n    visibility = [\"//testing:__subpackages__\"],\n)\n"
  },
  {
    "path": "testing/private/k8s_test_namespace.bzl",
    "content": "\"\"\"Rule for creating Kubernetes test namespaces.\"\"\"\n\ndef _k8s_test_namespace_impl(ctx):\n    files = []  # runfiles list\n\n    # add files referenced by rule attributes to runfiles\n    files = [ctx.file.kubectl, ctx.file.kubeconfig]\n\n    # create namespace reservation script\n    namespace_create = ctx.actions.declare_file(ctx.label.name + \".create\")\n    ctx.actions.expand_template(\n        template = ctx.file._namespace_template,\n        substitutions = {\n            \"%{kubeconfig}\": ctx.file.kubeconfig.path,\n            \"%{kubectl}\": ctx.file.kubectl.path,\n        },\n        output = namespace_create,\n        is_executable = True,\n    )\n    files.append(namespace_create)\n\n    return [DefaultInfo(\n        executable = namespace_create,\n        runfiles = ctx.runfiles(files = files),\n    )]\n\nk8s_test_namespace = rule(\n    attrs = {\n        \"kubeconfig\": attr.label(\n            allow_single_file = True,\n        ),\n        \"kubectl\": attr.label(\n            cfg = \"exec\",\n            executable = True,\n            allow_single_file = True,\n        ),\n        \"_namespace_template\": attr.label(\n            default = Label(\"//testing/private:k8s_test_namespace.sh.tpl\"),\n            allow_single_file = True,\n        ),\n    },\n    executable = True,\n    implementation = _k8s_test_namespace_impl,\n)\n"
  },
  {
    "path": "testing/private/k8s_test_namespace.sh.tpl",
    "content": "#!/usr/bin/env bash\n# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License. You may obtain a copy\n# of the License at http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software distributed under\n# the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n# OF ANY KIND, either express or implied. See the License for the specific language\n# governing permissions and limitations under the License.\n\n# TODO: disable trace\nset -euo pipefail\n[ -o xtrace ] && env\n\nfunction guess_runfiles() {\n    pushd ${BASH_SOURCE[0]}.runfiles > /dev/null 2>&1\n    pwd\n    popd > /dev/null 2>&1\n}\n\nRUNFILES=${TEST_SRCDIR:-$(guess_runfiles)}\nTEST_UNDECLARED_OUTPUTS_DIR=${TEST_UNDECLARED_OUTPUTS_DIR:-.}\n\nKUBECTL=%{kubectl}\nKUBECONFIG=%{kubeconfig}\nCLUSTER_FILE=%{cluster}\n\nSET_NAMESPACE=%{set_namespace}\nIT_MANIFEST_FILTER=%{it_manifest_filter}\n\n\nNAMESPACE_NAME_FILE=${TEST_UNDECLARED_OUTPUTS_DIR}/namespace\nKUBECONFIG_FILE=${TEST_UNDECLARED_OUTPUTS_DIR}/kubeconfig\n\n# get cluster and username from provided configuration\nCLUSTER=$(cat ${CLUSTER_FILE})\nUSER=$(${KUBECTL} --kubeconfig=${KUBECONFIG} config view -o jsonpath='{.users[?(@.name == '\"\\\"${CLUSTER}\\\")].name}\")\n\necho \"Cluster: ${CLUSTER}\" >&2\necho \"User: ${USER}\" >&2\n\nset +e\nif [ -n \"${K8S_MYNAMESPACE:-}\" ]\nthen\n    # do not create random namesspace\n    NAMESPACE=${USER}\n    # do not delete namespace after the test is complete\n    DELETE_NAMESPACE_FLAG=\"\"\nelse\n    # create random namespace\n    DELETE_NAMESPACE_FLAG=\"-delete_namespace\"\n    COUNT=\"0\"\n    while true; do\n        NAMESPACE=`whoami`-$(( (RANDOM) + 32767 ))\n        ${KUBECTL} --kubeconfig=${KUBECONFIG} --cluster=${CLUSTER} --user=${USER} create namespace ${NAMESPACE} && break\n        COUNT=$[$COUNT + 1]\n        if [ $COUNT -ge 10 ]; then\n            echo \"Unable to create namespace in $COUNT attempts!\" >&2\n            exit 1\n        fi\n    done\nfi\necho \"Namespace: ${NAMESPACE}\" >&2\nset -e\n\n# expose generated namespace name as rule output\nmkdir -p $(dirname $NAMESPACE_NAME_FILE)\necho $NAMESPACE > $NAMESPACE_NAME_FILE\n\n# create kubectl configuration copy with default context set to use newly created namespace\nmkdir -p $(dirname $KUBECONFIG_FILE)\ncat ${KUBECONFIG} > $KUBECONFIG_FILE\nexport KUBECONFIG=$KUBECONFIG_FILE\nCONTEXT=$CLUSTER-$NAMESPACE\n${KUBECTL} --cluster=$CLUSTER --user=$USER --namespace=$NAMESPACE config set-context $CONTEXT >&2\n${KUBECTL} config use-context $CONTEXT >&2\n\n# set runfiles for STMTS\nexport PYTHON_RUNFILES=${RUNFILES}\n\nPIDS=()\nfunction async() {\n    # Launch the command asynchronously and track its process id.\n    PYTHON_RUNFILES=${RUNFILES} \"$@\" &\n    PIDS+=($!)\n}\n\nfunction waitpids() {\n    # Wait for all of the subprocesses, failing the script if any of them failed.\n    if [ \"${#PIDS[@]}\" != 0 ]; then\n        for pid in ${PIDS[@]}; do\n            wait ${pid}\n        done\n    fi\n}\n\n%{push_statements}\n# create k8s objects\n%{statements}\n\n%{it_sidecar} -namespace=${NAMESPACE} -timeout=%{test_timeout} %{portforwards} %{waitforapps} ${DELETE_NAMESPACE_FLAG} \"$@\"\n"
  },
  {
    "path": "testing/private/k8s_test_setup.bzl",
    "content": "\"\"\"Rule for setting up Kubernetes test environments.\"\"\"\n\nload(\"//adapters:providers.bzl\", \"K8sPushInfo\")\nload(\"//kustomize:defs.bzl\", \"KustomizeInfo\")\n\ndef _image_push_statements(\n        _ctx,\n        kustomize_objs,\n        files = []):\n    statements = \"\"\n    trans_img_pushes = depset(transitive = [obj[KustomizeInfo].image_pushes for obj in kustomize_objs]).to_list()\n    statements += \"\\n\".join([\n        \"echo  pushing {}/{}\".format(exe[K8sPushInfo].registry, exe[K8sPushInfo].repository)\n        for exe in trans_img_pushes\n    ]) + \"\\n\"\n    statements += \"\\n\".join([\n        \"async \\\"%s\\\"\" % exe.files_to_run.executable.short_path\n        for exe in trans_img_pushes\n    ]) + \"\\nwaitpids\\n\"\n    files += [obj.files_to_run.executable for obj in trans_img_pushes]\n    dep_runfiles = [obj[DefaultInfo].default_runfiles for obj in trans_img_pushes]\n    return statements, files, dep_runfiles\n\ndef _k8s_test_setup_impl(ctx):\n    files = []  # runfiles list\n    transitive = []\n    commands = []  # the list of commands to execute\n\n    kustomize_executable = ctx.toolchains[\"@rules_gitops//kustomize:toolchain_type\"].kustomizeinfo.executable\n\n    # add files referenced by rule attributes to runfiles\n    files = [ctx.executable._stamper, ctx.file.kubectl, ctx.file.kubeconfig, kustomize_executable, ctx.executable._it_sidecar, ctx.executable._it_manifest_filter]\n    files += ctx.files._set_namespace\n    files += ctx.files.cluster\n\n    push_statements, files, pushes_runfiles = _image_push_statements(ctx, [o for o in ctx.attr.objects if KustomizeInfo in o], files)\n\n    # execute all objects targets\n    for obj in ctx.attr.objects:\n        if obj.files_to_run.executable:\n            # add object' targets and excutables to runfiles\n            files.append(obj.files_to_run.executable)\n            transitive.append(obj.default_runfiles.files)\n\n            # add object' execution command\n            commands.append(obj.files_to_run.executable.short_path + \" | ${SET_NAMESPACE} $NAMESPACE | ${IT_MANIFEST_FILTER} | ${KUBECTL} apply -f -\")\n        else:\n            files += obj.files.to_list()\n            commands += [ctx.executable._template_engine.short_path + \" --template=\" + filename.short_path + \" --variable=NAMESPACE=${NAMESPACE} | ${SET_NAMESPACE} $NAMESPACE | ${IT_MANIFEST_FILTER} | ${KUBECTL} apply -f -\" for filename in obj.files.to_list()]\n\n    files.append(ctx.executable._template_engine)\n\n    # create namespace script\n    ctx.actions.expand_template(\n        template = ctx.file._namespace_template,\n        substitutions = {\n            \"%{it_sidecar}\": ctx.executable._it_sidecar.short_path,\n            \"%{cluster}\": ctx.file.cluster.path,\n            \"%{kubeconfig}\": ctx.file.kubeconfig.path,\n            \"%{kubectl}\": ctx.file.kubectl.path,\n            \"%{portforwards}\": \" \".join([\"-portforward=\" + p for p in ctx.attr.portforward_services]),\n            \"%{push_statements}\": push_statements,\n            \"%{set_namespace}\": ctx.executable._set_namespace.short_path,\n            \"%{it_manifest_filter}\": ctx.executable._it_manifest_filter.short_path,\n            \"%{statements}\": \"\\n\".join(commands),\n            \"%{test_timeout}\": ctx.attr.setup_timeout,\n            \"%{waitforapps}\": \" \".join([\"-waitforapp=\" + p for p in ctx.attr.wait_for_apps]),\n        },\n        output = ctx.outputs.executable,\n    )\n\n    rf = ctx.runfiles(files = files, transitive_files = depset(transitive = transitive))\n    rf = rf.merge(ctx.attr._set_namespace[DefaultInfo].default_runfiles)\n    for dep_rf in pushes_runfiles:\n        rf = rf.merge(dep_rf)\n    return [DefaultInfo(\n        executable = ctx.outputs.executable,\n        runfiles = rf,\n    )]\n\nk8s_test_setup = rule(\n    attrs = {\n        \"kubeconfig\": attr.label(\n            default = Label(\"@k8s_test//:kubeconfig\"),\n            allow_single_file = True,\n        ),\n        \"kubectl\": attr.label(\n            default = Label(\"@k8s_test//:kubectl\"),\n            cfg = \"exec\",\n            executable = True,\n            allow_single_file = True,\n        ),\n        \"objects\": attr.label_list(\n            cfg = \"target\",\n        ),\n        \"portforward_services\": attr.string_list(),\n        \"setup_timeout\": attr.string(default = \"10m\"),\n        \"wait_for_apps\": attr.string_list(),\n        \"cluster\": attr.label(\n            default = Label(\"@k8s_test//:cluster\"),\n            allow_single_file = True,\n        ),\n        \"_it_sidecar\": attr.label(\n            default = Label(\"//testing/it_sidecar:it_sidecar\"),\n            cfg = \"exec\",\n            executable = True,\n        ),\n        \"_namespace_template\": attr.label(\n            default = Label(\"//testing/private:k8s_test_namespace.sh.tpl\"),\n            allow_single_file = True,\n        ),\n        \"_set_namespace\": attr.label(\n            default = Label(\"//testing/private:set_namespace\"),\n            cfg = \"exec\",\n            executable = True,\n        ),\n        \"_it_manifest_filter\": attr.label(\n            default = Label(\"//testing/it_manifest_filter:it_manifest_filter\"),\n            cfg = \"exec\",\n            executable = True,\n        ),\n        \"_stamper\": attr.label(\n            default = Label(\"//stamper:stamper\"),\n            cfg = \"exec\",\n            executable = True,\n            allow_files = True,\n        ),\n        \"_template_engine\": attr.label(\n            default = Label(\"//templating:fast_template_engine\"),\n            executable = True,\n            cfg = \"exec\",\n        ),\n    },\n    executable = True,\n    implementation = _k8s_test_setup_impl,\n    toolchains = [\"@rules_gitops//kustomize:toolchain_type\"],\n)\n"
  },
  {
    "path": "testing/private/set_namespace.sh",
    "content": "#!/usr/bin/env bash\n# Copyright 2026 Adobe. All rights reserved.\n# This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License. You may obtain a copy\n# of the License at http://www.apache.org/licenses/LICENSE-2.0\n\n# Unless required by applicable law or agreed to in writing, software distributed under\n# the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n# OF ANY KIND, either express or implied. See the License for the specific language\n# governing permissions and limitations under the License.\n\nset +x\n\n# --- begin runfiles.bash initialization v3 ---\n# Copy-pasted from the Bazel Bash runfiles library v3.\nset -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash\nsource \"${RUNFILES_DIR:-/dev/null}/$f\" 2>/dev/null || \\\n  source \"$(grep -sm1 \"^$f \" \"${RUNFILES_MANIFEST_FILE:-/dev/null}\" | cut -f2- -d' ')\" 2>/dev/null || \\\n  source \"$0.runfiles/$f\" 2>/dev/null || \\\n  source \"$(grep -sm1 \"^$f \" \"$0.runfiles_manifest\" | cut -f2- -d' ')\" 2>/dev/null || \\\n  source \"$(grep -sm1 \"^$f \" \"$0.exe.runfiles_manifest\" | cut -f2- -d' ')\" 2>/dev/null || \\\n  { echo>&2 \"ERROR: cannot find $f\"; exit 1; }; f=; set -e\n# --- end runfiles.bash initialization v3 ---\n\nif [ \"$1\" == \"\" ]; then\n    echo usage:\n    echo $0 'namespace <in.yaml >out.yaml'\n    exit 1\nfi\nset -euo pipefail\ndir=$(mktemp -d)\ncat >${dir}/in.yaml\ncat >${dir}/kustomization.yaml <<EOF\nnamespace: $1\nresources:\n- in.yaml\nEOF\nKUSTOMIZE_BIN=\"$(rlocation rules_gitops/testing/private/kustomize_bin)\"\n\nexec $KUSTOMIZE_BIN build ${dir}\n"
  },
  {
    "path": "tests/BUILD.bazel",
    "content": "load(\"@bazel_lib//lib:diff_test.bzl\", \"diff_test\")\nload(\"@bazel_tools//tools/build_rules:test_rules.bzl\", \"rule_test\")\nload(\"//gitops:defs.bzl\", \"gitops\")\nload(\"//kubectl:defs.bzl\", \"kubectl_binary\")\nload(\"//kustomize:defs.bzl\", \"kustomization\")\nload(\"//tools:util.bzl\", \"golden_test\")\n\ngenrule(\n    name = \"set_namespace\",\n    srcs = [\n        \"test.yaml\",\n    ],\n    outs = [\"test_replaced.yaml\"],\n    cmd = \"cat $(location test.yaml) | $(location //testing/private:set_namespace) newnamespace-1 > $@\",\n    tools = [\n        \"//testing/private:set_namespace\",\n    ],\n)\n\ndiff_test(\n    name = \"set_namespace_test\",\n    file1 = \"set_namespace\",\n    file2 = \"test_expected.yaml\",\n)\n\n#-------------------\n\nkustomization(\n    name = \"namespace\",\n    testonly = True,\n    images = [\n        \"//tests/images:k8s_image\",\n    ],\n    manifests = [\n        \"deployment.yaml\",\n        \"service.yaml\",\n        \"crb.yaml\",\n    ],\n    namespace = \"bs-dev\",\n)\n\ngolden_test(\n    name = \"namespace_test\",\n    extension = \".yaml\",\n    in_file = \"namespace\",\n)\n\n#-------------------\n\nkustomization(\n    name = \"raw\",\n    testonly = True,\n    images = [\n        \"//tests/images:k8s_image\",\n    ],\n    manifests = [\n        \"crb.yaml\",\n        \"deployment.yaml\",\n        \"service.yaml\",\n    ],\n    namespace = \"\",\n)\n\nrule_test(\n    name = \"raw_generates_yaml\",\n    generates = [\"raw.yaml\"],\n    rule = \"raw\",\n)\n\ngolden_test(\n    name = \"raw_test\",\n    extension = \".yaml\",\n    in_file = \"raw\",\n)\n\n#-------------------\n\nkustomization(\n    name = \"image_resolved\",\n    testonly = True,\n    images = [\n        \"//tests/images:k8s_image\",\n    ],\n    manifests = [\n        \"deployment.yaml\",\n        \"service.yaml\",\n        \"crb.yaml\",\n    ],\n    namespace = \"\",\n)\n\ngenrule(\n    name = \"expected_image_resolved\",\n    srcs = [\n        \":expected_image_resolved_test.tpl.yaml\",\n        \"//tests/images:image.digest\",\n    ],\n    outs = [\"goldens/expected_image_resolved.golden.yaml\"],\n    cmd = \" \".join([\n        \"./$(location //templating:fast_template_engine)\",\n        \"--template=$(location expected_image_resolved_test.tpl.yaml)\",\n        \"--variable=IMAGE_DIGEST=$$(cat $(location //tests/images:image.digest))\",\n        \"--output=$@\",\n    ]),\n    tools = [\n        \"//templating:fast_template_engine\",\n    ],\n)\n\ngolden_test(\n    name = \"expected_image_resolved_test\",\n    extension = \".yaml\",\n    in_file = \"image_resolved\",\n)\n\ndiff_test(\n    name = \"image_resolved_conformance\",\n    # ignore changes in the amount of white space\n    diff_args = [\"-b\"],\n    file1 = \"image_resolved\",\n    file2 = \"expected_image_resolved\",\n)\n\n#-------------------\n\nkustomization(\n    name = \"image_digest\",\n    testonly = True,\n    images = [\n        \"//tests/images:k8s_image\",\n    ],\n    manifests = [\n        \"deployment.yaml\",\n        \"service.yaml\",\n        \"crb.yaml\",\n    ],\n    namespace = \"\",\n)\n\ngolden_test(\n    name = \"image_digest_test\",\n    extension = \".yaml\",\n    in_file = \"image_digest\",\n)\n\n#-------------------\n\nkustomization(\n    name = \"configmap\",\n    testonly = True,\n    configmaps_srcs = glob([\"configmaps/**/*\"]),\n    namespace = \"\",\n)\n\ngolden_test(\n    name = \"configmap_test\",\n    extension = \".yaml\",\n    in_file = \"configmap\",\n)\n\n#-------------------\n\nkustomization(\n    name = \"secrets\",\n    testonly = True,\n    namespace = \"\",\n    secrets_srcs = glob([\"secrets/**/*\"]),\n)\n\ngolden_test(\n    name = \"secrets_test\",\n    extension = \".yaml\",\n    in_file = \"secrets\",\n)\n\n#-------------------\n\n## TODO\n\nkustomization(\n    name = \"integration_test\",\n    testonly = True,\n    images = [\n        \"//tests/images:k8s_image\",\n    ],\n    manifests = [\n        \"deployment.yaml\",\n        \"service.yaml\",\n        \":configmap\",\n    ],\n    namespace = \"{BUILD_USER}\",\n)\n\nkubectl_binary(\n    name = \"mynamespace.apply\",\n    testonly = True,\n    srcs = [\":integration_test\"],\n    cluster = \"dev3\",\n    namespace = \"test\",\n)\n\ngitops(\n    name = \"dev-something.gitops\",\n    testonly = True,\n    srcs = [\":namespace\"],\n    cluster = \"dev\",\n    namespace = \"test\",\n)\n\n#-------------------\n\nkustomization(\n    name = \"name_prefix\",\n    manifests = [\"test.yaml\"],\n    name_prefix = \"prefix-\",\n    namespace = \"\",\n)\n\ngolden_test(\n    name = \"name_prefix_test\",\n    extension = \".yaml\",\n    in_file = \"name_prefix\",\n)\n\n#-------------------\n\nkustomization(\n    name = \"name_suffix\",\n    manifests = [\"test.yaml\"],\n    name_suffix = \"-suffix\",\n    namespace = \"\",\n)\n\ngolden_test(\n    name = \"name_suffix_test\",\n    extension = \".yaml\",\n    in_file = \"name_suffix\",\n)\n\n#-------------------\n\nkustomization(\n    name = \"patch\",\n    images = [\n        \"//tests/images:k8s_image\",\n    ],\n    manifests = [\"deployment.yaml\"],\n    namespace = \"\",\n    patches = [\"overlay/deployment.yaml\"],\n)\n\ngenrule(\n    name = \"expected_patch\",\n    srcs = [\n        \":expected_patch.tpl.yaml\",\n        \"//tests/images:image.digest\",\n    ],\n    outs = [\"goldens/expected_patch.golden.yaml\"],\n    cmd = \" \".join([\n        \"./$(location //templating:fast_template_engine)\",\n        \"--template=$(location expected_patch.tpl.yaml)\",\n        \"--variable=IMAGE_DIGEST=$$(cat $(location //tests/images:image.digest))\",\n        \"--output=$@\",\n    ]),\n    tools = [\n        \"//templating:fast_template_engine\",\n    ],\n)\n\ndiff_test(\n    name = \"patch_conformance\",\n    # ignore changes in the amount of white space\n    diff_args = [\"-b\"],\n    file1 = \"patch\",\n    file2 = \"expected_patch\",\n)\n\ngolden_test(\n    name = \"expected_patch_test\",\n    extension = \".yaml\",\n    in_file = \"patch\",\n)\n\n#-------------------\n\nkustomization(\n    name = \"deployment_prefix_compat\",\n    manifests = [\"deployment_with_labels.yaml\"],\n    name_prefix = \"prefix-\",\n    namespace = \"\",\n)\n\ngolden_test(\n    name = \"deployment_prefix_compat_test\",\n    extension = \".yaml\",\n    in_file = \"deployment_prefix_compat\",\n)\n\n#-------------------\n\nkustomization(\n    name = \"deployment_suffix_compat\",\n    manifests = [\"deployment_with_labels.yaml\"],\n    name_suffix = \"-suffix\",\n    namespace = \"\",\n)\n\ngolden_test(\n    name = \"deployment_suffix_compat_test\",\n    extension = \".yaml\",\n    in_file = \"deployment_suffix_compat\",\n)\n\n#-------------------\n\nkustomization(\n    name = \"deployment_prefix\",\n    configurations = [\"//gitops:nameprefix_deployment_labels_config.yaml\"],\n    manifests = [\"deployment_with_labels.yaml\"],\n    name_prefix = \"prefix-\",\n    namespace = \"\",\n)\n\ngolden_test(\n    name = \"deployment_prefix_test\",\n    extension = \".yaml\",\n    in_file = \"deployment_prefix\",\n)\n\n#-------------------\n\nkustomization(\n    name = \"deployment_suffix\",\n    configurations = [\"//gitops:namesuffix_deployment_labels_config.yaml\"],\n    manifests = [\"deployment_with_labels.yaml\"],\n    name_suffix = \"-suffix\",\n    namespace = \"\",\n)\n\ngolden_test(\n    name = \"deployment_suffix_test\",\n    extension = \".yaml\",\n    in_file = \"deployment_suffix\",\n)\n\n#-------------------\n\nkustomization(\n    name = \"common_labels\",\n    common_annotations = {\"ownerTeam\": \"apps\"},\n    common_labels = {\"flavor\": \"canary\"},\n    manifests = [\n        \"deployment_with_labels.yaml\",\n        \"service.yaml\",\n    ],\n    namespace = \"\",\n)\n\ngolden_test(\n    name = \"common_labels_test\",\n    extension = \".yaml\",\n    in_file = \"common_labels\",\n)\n\n#-------------------\n\nkustomization(\n    name = \"patch_images\",\n    image_name_patches = {\n        \"busybox\": \"alpine\",\n        \"debian\": \"ubuntu\",\n    },\n    image_tag_patches = {\n        \"busybox\": \"3\",\n    },\n    manifests = [\n        \"job.yaml\",\n    ],\n)\n\ngolden_test(\n    name = \"patch_images_test\",\n    extension = \".yaml\",\n    in_file = \"patch_images\",\n)\n"
  },
  {
    "path": "tests/configmaps/mapa/mapa1.properties",
    "content": "prop1=a1\nprop2=a1too\n"
  },
  {
    "path": "tests/configmaps/mapa/mapa2.properties",
    "content": "prop=a2\n"
  },
  {
    "path": "tests/configmaps/mapb/config.yaml",
    "content": "someshit:\n  - one\n  - two\n"
  },
  {
    "path": "tests/crb.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: crb-name\nsubjects:\n- kind: Group\n  name: crb-subject\nroleRef:\n  kind: ClusterRole\n  name: cluster-admin\n"
  },
  {
    "path": "tests/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp\nspec:\n  selector:\n    matchLabels:\n      app: myapp\n  template:\n    metadata:\n      labels:\n        app: myapp\n    spec:\n      containers:\n      - name: myapp\n        image: //tests/images:image\n"
  },
  {
    "path": "tests/deployment_with_labels.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp\nspec:\n  selector:\n    matchLabels:\n      app: myapp\n  template:\n    metadata:\n      labels:\n        app: myapp\n    spec:\n      containers:\n      - name: myapp\n        image: test-image\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp2\nspec:\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: myapp\n  template:\n    metadata:\n      labels:\n        app.kubernetes.io/name: myapp\n    spec:\n      containers:\n      - name: myapp\n        image: test-image\n"
  },
  {
    "path": "tests/expected_image_resolved_test.tpl.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: crb-name\nroleRef:\n  kind: ClusterRole\n  name: cluster-admin\nsubjects:\n- kind: Group\n  name: crb-subject\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: myapp\nspec:\n  ports:\n  - name: web\n    port: 80\n    targetPort: 8080\n  selector:\n    app: myapp\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp\nspec:\n  selector:\n    matchLabels:\n      app: myapp\n  template:\n    metadata:\n      labels:\n        app: myapp\n    spec:\n      containers:\n        - image: localhost:15000/rules_gitops/tests/images/image@{{IMAGE_DIGEST}}\n          name: myapp\n"
  },
  {
    "path": "tests/expected_patch.tpl.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp\nspec:\n  selector:\n    matchLabels:\n      app: myapp\n  template:\n    metadata:\n      labels:\n        app: myapp\n    spec:\n      containers:\n        - image: localhost:15000/rules_gitops/tests/images/image@{{IMAGE_DIGEST}}\n          name: myapp\n          resources:\n            limits:\n              cpu: \"1\"\n              memory: 4Gi\n            requests:\n              cpu: \"1\"\n              memory: 2Gi\n"
  },
  {
    "path": "tests/goldens/common_labels.golden.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  annotations:\n    ownerTeam: apps\n  labels:\n    flavor: canary\n  name: myapp\nspec:\n  ports:\n  - name: web\n    port: 80\n    targetPort: 8080\n  selector:\n    app: myapp\n    flavor: canary\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    ownerTeam: apps\n  labels:\n    flavor: canary\n  name: myapp\nspec:\n  selector:\n    matchLabels:\n      app: myapp\n      flavor: canary\n  template:\n    metadata:\n      annotations:\n        ownerTeam: apps\n      labels:\n        app: myapp\n        flavor: canary\n    spec:\n      containers:\n      - image: test-image\n        name: myapp\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    ownerTeam: apps\n  labels:\n    flavor: canary\n  name: myapp2\nspec:\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: myapp\n      flavor: canary\n  template:\n    metadata:\n      annotations:\n        ownerTeam: apps\n      labels:\n        app.kubernetes.io/name: myapp\n        flavor: canary\n    spec:\n      containers:\n      - image: test-image\n        name: myapp\n"
  },
  {
    "path": "tests/goldens/configmap.golden.yaml",
    "content": "apiVersion: v1\ndata:\n  mapa1.properties: |\n    prop1=a1\n    prop2=a1too\n  mapa2.properties: |\n    prop=a2\nkind: ConfigMap\nmetadata:\n  name: mapa\n---\napiVersion: v1\ndata:\n  config.yaml: |\n    someshit:\n      - one\n      - two\nkind: ConfigMap\nmetadata:\n  name: mapb\n"
  },
  {
    "path": "tests/goldens/deployment_prefix.golden.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: prefix-myapp\nspec:\n  selector:\n    matchLabels:\n      app: prefix-myapp\n  template:\n    metadata:\n      labels:\n        app: prefix-myapp\n    spec:\n      containers:\n      - image: test-image\n        name: myapp\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: prefix-myapp2\nspec:\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: prefix-myapp\n  template:\n    metadata:\n      labels:\n        app.kubernetes.io/name: prefix-myapp\n    spec:\n      containers:\n      - image: test-image\n        name: myapp\n"
  },
  {
    "path": "tests/goldens/deployment_prefix_compat.golden.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: prefix-myapp\nspec:\n  selector:\n    matchLabels:\n      app: myapp\n  template:\n    metadata:\n      labels:\n        app: myapp\n    spec:\n      containers:\n      - image: test-image\n        name: myapp\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: prefix-myapp2\nspec:\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: myapp\n  template:\n    metadata:\n      labels:\n        app.kubernetes.io/name: myapp\n    spec:\n      containers:\n      - image: test-image\n        name: myapp\n"
  },
  {
    "path": "tests/goldens/deployment_suffix.golden.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp-suffix\nspec:\n  selector:\n    matchLabels:\n      app: myapp-suffix\n  template:\n    metadata:\n      labels:\n        app: myapp-suffix\n    spec:\n      containers:\n      - image: test-image\n        name: myapp\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp2-suffix\nspec:\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: myapp-suffix\n  template:\n    metadata:\n      labels:\n        app.kubernetes.io/name: myapp-suffix\n    spec:\n      containers:\n      - image: test-image\n        name: myapp\n"
  },
  {
    "path": "tests/goldens/deployment_suffix_compat.golden.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp-suffix\nspec:\n  selector:\n    matchLabels:\n      app: myapp\n  template:\n    metadata:\n      labels:\n        app: myapp\n    spec:\n      containers:\n      - image: test-image\n        name: myapp\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp2-suffix\nspec:\n  selector:\n    matchLabels:\n      app.kubernetes.io/name: myapp\n  template:\n    metadata:\n      labels:\n        app.kubernetes.io/name: myapp\n    spec:\n      containers:\n      - image: test-image\n        name: myapp\n"
  },
  {
    "path": "tests/goldens/image_digest.golden.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: crb-name\nroleRef:\n  kind: ClusterRole\n  name: cluster-admin\nsubjects:\n- kind: Group\n  name: crb-subject\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: myapp\nspec:\n  ports:\n  - name: web\n    port: 80\n    targetPort: 8080\n  selector:\n    app: myapp\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp\nspec:\n  selector:\n    matchLabels:\n      app: myapp\n  template:\n    metadata:\n      labels:\n        app: myapp\n    spec:\n      containers:\n      - image: localhost:15000/rules_gitops/tests/images/image@sha256:1e45ec723bb85e017c2a4dc6c4cc3a75b03cda095df5df8c5ebf8ba665dd2501\n        name: myapp\n"
  },
  {
    "path": "tests/goldens/image_resolved.golden.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: crb-name\nroleRef:\n  kind: ClusterRole\n  name: cluster-admin\nsubjects:\n- kind: Group\n  name: crb-subject\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: myapp\nspec:\n  ports:\n  - name: web\n    port: 80\n    targetPort: 8080\n  selector:\n    app: myapp\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp\nspec:\n  selector:\n    matchLabels:\n      app: myapp\n  template:\n    metadata:\n      labels:\n        app: myapp\n    spec:\n      containers:\n      - image: localhost:15000/rules_gitops/tests/images/image@sha256:1e45ec723bb85e017c2a4dc6c4cc3a75b03cda095df5df8c5ebf8ba665dd2501\n        name: myapp\n"
  },
  {
    "path": "tests/goldens/name_prefix.golden.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: prefix-kubedobe:cluster-admin\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: cluster-admin\nsubjects:\n- apiGroup: rbac.authorization.k8s.io\n  kind: Group\n  name: adcloud_k8s_sudo\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    k8s-app: kube-dns\n  name: prefix-akube-dns\n  namespace: kube-system\nspec:\n  ports:\n  - name: dns\n    port: 53\n  selector:\n    k8s-app: kube-dns\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    k8s-app: kube-dns\n  name: prefix-bkube-dns\nspec:\n  ports:\n  - name: dns\n    port: 53\n  selector:\n    k8s-app: kube-dns\n"
  },
  {
    "path": "tests/goldens/name_suffix.golden.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: kubedobe:cluster-admin-suffix\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: cluster-admin\nsubjects:\n- apiGroup: rbac.authorization.k8s.io\n  kind: Group\n  name: adcloud_k8s_sudo\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    k8s-app: kube-dns\n  name: akube-dns-suffix\n  namespace: kube-system\nspec:\n  ports:\n  - name: dns\n    port: 53\n  selector:\n    k8s-app: kube-dns\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    k8s-app: kube-dns\n  name: bkube-dns-suffix\nspec:\n  ports:\n  - name: dns\n    port: 53\n  selector:\n    k8s-app: kube-dns\n"
  },
  {
    "path": "tests/goldens/namespace.golden.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: crb-name\nroleRef:\n  kind: ClusterRole\n  name: cluster-admin\nsubjects:\n- kind: Group\n  name: crb-subject\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: myapp\n  namespace: bs-dev\nspec:\n  ports:\n  - name: web\n    port: 80\n    targetPort: 8080\n  selector:\n    app: myapp\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp\n  namespace: bs-dev\nspec:\n  selector:\n    matchLabels:\n      app: myapp\n  template:\n    metadata:\n      labels:\n        app: myapp\n    spec:\n      containers:\n      - image: localhost:15000/rules_gitops/tests/images/image@sha256:1e45ec723bb85e017c2a4dc6c4cc3a75b03cda095df5df8c5ebf8ba665dd2501\n        name: myapp\n"
  },
  {
    "path": "tests/goldens/patch.golden.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp\nspec:\n  selector:\n    matchLabels:\n      app: myapp\n  template:\n    metadata:\n      labels:\n        app: myapp\n    spec:\n      containers:\n      - image: localhost:15000/rules_gitops/tests/images/image@sha256:1e45ec723bb85e017c2a4dc6c4cc3a75b03cda095df5df8c5ebf8ba665dd2501\n        name: myapp\n        resources:\n          limits:\n            cpu: \"1\"\n            memory: 4Gi\n          requests:\n            cpu: \"1\"\n            memory: 2Gi\n"
  },
  {
    "path": "tests/goldens/patch_images.golden.yaml",
    "content": "apiVersion: batch/v1\nkind: Job\nmetadata:\n  name: hello\nspec:\n  template:\n    spec:\n      containers:\n      - command:\n        - sh\n        - -c\n        - echo \"Hello, Kubernetes!\" && sleep 3600\n        image: alpine:3\n        name: hello\n      - command:\n        - sh\n        - -c\n        - echo \"Hello, Kubernetes!\" && sleep 3600\n        image: ubuntu:latest\n        name: hello2\n      restartPolicy: OnFailure\n"
  },
  {
    "path": "tests/goldens/raw.golden.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: crb-name\nroleRef:\n  kind: ClusterRole\n  name: cluster-admin\nsubjects:\n- kind: Group\n  name: crb-subject\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: myapp\nspec:\n  ports:\n  - name: web\n    port: 80\n    targetPort: 8080\n  selector:\n    app: myapp\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp\nspec:\n  selector:\n    matchLabels:\n      app: myapp\n  template:\n    metadata:\n      labels:\n        app: myapp\n    spec:\n      containers:\n      - image: localhost:15000/rules_gitops/tests/images/image@sha256:1e45ec723bb85e017c2a4dc6c4cc3a75b03cda095df5df8c5ebf8ba665dd2501\n        name: myapp\n"
  },
  {
    "path": "tests/goldens/secrets.golden.yaml",
    "content": "apiVersion: v1\ndata:\n  file1.yaml: ZmlsZTEncyB2YWx1ZQ==\nkind: Secret\nmetadata:\n  name: secreta\ntype: Opaque\n---\napiVersion: v1\ndata:\n  file2.txt: ZmlsZTIncyB2YWx1ZQ==\n  foo: YmFy\nkind: Secret\nmetadata:\n  name: secretb\ntype: Opaque\n"
  },
  {
    "path": "tests/images/BUILD.bazel",
    "content": "load(\"@rules_gitops//adapters:rules_img.bzl\", \"k8s_push_info\")\nload(\"@rules_img//img:image.bzl\", \"image_manifest\")\nload(\"@rules_img//img:layer.bzl\", \"image_layer\")\nload(\"@rules_img//img:push.bzl\", \"image_push\")\n\npackage(\n    default_visibility = [\n        \"//kustomize/private/tests:__subpackages__\",\n        \"//tests:__subpackages__\",\n    ],\n)\n\nREGISTRY = \"localhost:15000\"\n\nplatform(\n    name = \"linux_amd64\",\n    constraint_values = [\n        \"@platforms//os:linux\",\n        \"@platforms//cpu:x86_64\",\n    ],\n)\n\nimage_layer(\n    name = \"base\",\n    srcs = {\n        \"/bin/app\": \"container_content.txt\",\n    },\n)\n\nimage_manifest(\n    name = \"image\",\n    base = \"@alpine\",\n    entrypoint = [\"/bin/sh\"],\n    layers = [\n        \":base\",\n    ],\n    platform = \":linux_amd64\",\n)\n\nfilegroup(\n    name = \"image.digest\",\n    srcs = [\n        \":image\",\n    ],\n    output_group = \"digest\",\n)\n\nimage_push(\n    name = \"push\",\n    image = \":image\",\n    registry = REGISTRY,\n    repository = \"rules_gitops/tests/images/image\",\n    visibility = [\"//:__pkg__\"],\n)\n\nk8s_push_info(\n    name = \"k8s_image\",\n    image = \":image\",\n    push = \":push\",\n    registry = REGISTRY,\n    repository = \"rules_gitops/tests/images/image\",\n)\n"
  },
  {
    "path": "tests/images/container_content.txt",
    "content": "this file goes into the test container\n"
  },
  {
    "path": "tests/job.yaml",
    "content": "apiVersion: batch/v1\nkind: Job\nmetadata:\n  name: hello\nspec:\n  template:\n    spec:\n      containers:\n      - name: hello\n        image: busybox\n        command: ['sh', '-c', 'echo \"Hello, Kubernetes!\" && sleep 3600']\n      - name: hello2\n        image: debian:latest\n        command: ['sh', '-c', 'echo \"Hello, Kubernetes!\" && sleep 3600']\n      restartPolicy: OnFailure\n"
  },
  {
    "path": "tests/overlay/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: myapp\nspec:\n  template:\n    spec:\n      containers:\n      - name: myapp\n        resources:\n          requests:\n            cpu: '1'\n            memory: 2Gi\n          limits:\n            cpu: '1'\n            memory: 4Gi\n"
  },
  {
    "path": "tests/secrets/secreta/file1.yaml",
    "content": "file1's value"
  },
  {
    "path": "tests/secrets/secretb/file2.txt",
    "content": "file2's value"
  },
  {
    "path": "tests/secrets/secretb/foo",
    "content": "bar"
  },
  {
    "path": "tests/service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: myapp\nspec:\n  ports:\n  - port: 80\n    name: web\n    targetPort: 8080\n  selector:\n    app: myapp\n"
  },
  {
    "path": "tests/set_namespace_test.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\nfunction test_namespace_replaced {\n  OUTPUT=`cat tests/test.yaml | testing/private/set_namespace newnamespace-1`\n  EXPECTED_OUTPUT=$(cat tests/test_expected.yaml)\n  if [ \"${OUTPUT}\" != \"${EXPECTED_OUTPUT}\" ]; then\n    echo Unexpected set_namespace output:\n    echo $OUTPUT\n    exit 1\n  fi\n}\n\ntest_namespace_replaced\n"
  },
  {
    "path": "tests/test.yaml",
    "content": "# namespace should be replaced\napiVersion: v1\nkind: Service\nmetadata:\n  name: akube-dns\n  namespace: kube-system\n  labels:\n    k8s-app: kube-dns\nspec:\n  selector:\n    k8s-app: kube-dns\n  ports:\n  - name: dns\n    port: 53\n---\n#namespace should be set\napiVersion: v1\nkind: Service\nmetadata:\n  name: bkube-dns\n  labels:\n    k8s-app: kube-dns\nspec:\n  selector:\n    k8s-app: kube-dns\n  ports:\n  - name: dns\n    port: 53\n---\n# should be NO namespace since this object is globally scoped\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: kubedobe:cluster-admin\nsubjects:\n- kind: Group\n  name: adcloud_k8s_sudo\n  apiGroup: rbac.authorization.k8s.io\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: cluster-admin\n"
  },
  {
    "path": "tests/test_expected.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: kubedobe:cluster-admin\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: cluster-admin\nsubjects:\n- apiGroup: rbac.authorization.k8s.io\n  kind: Group\n  name: adcloud_k8s_sudo\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    k8s-app: kube-dns\n  name: akube-dns\n  namespace: newnamespace-1\nspec:\n  ports:\n  - name: dns\n    port: 53\n  selector:\n    k8s-app: kube-dns\n---\napiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    k8s-app: kube-dns\n  name: bkube-dns\n  namespace: newnamespace-1\nspec:\n  ports:\n  - name: dns\n    port: 53\n  selector:\n    k8s-app: kube-dns\n"
  },
  {
    "path": "tools/BUILD.bazel",
    "content": "load(\"@bazel_lib//:bzl_library.bzl\", \"bzl_library\")\nload(\"@bazelrc-preset.bzl\", \"bazelrc_preset\")\nload(\"@preset.bzl\", \"PRESET_FILE\")\n\nbazelrc_preset(\n    name = \"preset\",\n    doc_link_template = \"https://registry.build/flag/bazel?filter={flag}\",\n    out_file = PRESET_FILE,\n)\n\nbzl_library(\n    name = \"util\",\n    srcs = [\"util.bzl\"],\n    visibility = [\"//visibility:public\"],\n    deps = [\"@bazel_lib//lib:write_source_files\"],\n)\n"
  },
  {
    "path": "tools/bazel",
    "content": "#!/usr/bin/env bash\n\n# Determine which preset bazelrc to use based on USE_BAZEL_VERSION\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nPRESET_LINK=\"${SCRIPT_DIR}/preset.bazelrc\"\n\nif [[ -z \"${USE_BAZEL_VERSION}\" ]]; then\n  TARGET=\"preset7.bazelrc\"\nelif [[ \"${USE_BAZEL_VERSION}\" == 8* ]]; then\n  TARGET=\"preset8.bazelrc\"\nelif [[ \"${USE_BAZEL_VERSION}\" == 9* ]]; then\n  TARGET=\"preset9.bazelrc\"\nelse\n  TARGET=\"preset7.bazelrc\"\nfi\n\nln -sf \"${TARGET}\" \"${PRESET_LINK}\"\n\n\"${BAZEL_REAL}\" \"$@\""
  },
  {
    "path": "tools/preset.bzl",
    "content": "\"Repository rule to selectively determine which preset file is active\"\n\ndef _preset_impl(rctx):\n    version = rctx.getenv(\"USE_BAZEL_VERSION\")\n\n    preset_bzl_content = \"\"\n\n    if version and version.startswith(\"8\"):\n        preset_bzl_content += \"\"\"PRESET_FILE = \"preset8.bazelrc\\\"\"\"\"\n    elif version and version.startswith(\"9\"):\n        preset_bzl_content += \"\"\"PRESET_FILE = \"preset9.bazelrc\\\"\"\"\"\n    else:\n        preset_bzl_content += \"\"\"PRESET_FILE = \"preset7.bazelrc\\\"\"\"\"\n\n    rctx.file(\"BUILD.bazel\", \"\"\"exports_files([\"preset.bzl\"])\"\"\")\n    rctx.file(\"preset.bzl\", preset_bzl_content)\n\npreset = repository_rule(\n    implementation = _preset_impl,\n)\n"
  },
  {
    "path": "tools/preset7.bazelrc",
    "content": "# Generated by bazelrc-preset.bzl\n# To update this file, run:\n#   bazel run @@//tools:preset.update\n\n# On CI, announce all announces command options read from the bazelrc file(s) when starting up at the\n# beginning of each Bazel invocation. This is very useful on CI to be able to inspect which flags\n# are being applied on each run based on the order of overrides.\ncommon:ci --announce_rc\n# Docs: https://registry.build/flag/bazel?filter=announce_rc\n\n# Avoid creating a runfiles tree for binaries or tests until it is needed.\n# See https://github.com/bazelbuild/bazel/issues/6627\n# This may break local workflows that `build` a binary target, then run the resulting program outside of `bazel run`.\n# In those cases, the script will need to call `bazel build --build_runfile_links //my/binary:target` and then execute the resulting program.\ncommon --nobuild_runfile_links\n# Docs: https://registry.build/flag/bazel?filter=build_runfile_links\n\n# See https://github.com/bazelbuild/bazel/issues/20577\ncoverage --build_runfile_links\n# Docs: https://registry.build/flag/bazel?filter=build_runfile_links\n\n# Always run tests even if they have cached results.\n# This ensures tests are executed fresh each time, useful for debugging and ensuring test reliability.\ncommon:debug --nocache_test_results\n# Docs: https://registry.build/flag/bazel?filter=cache_test_results\n\n# Don’t encourage a rules author to update their deps if not needed.\n# These bazel_dep calls should indicate the minimum version constraint of the ruleset.\n# If the author instead updates to the newest of any of their transitives, as this flag would suggest,\n# then they'll also force their dependents to a newer version.\n# Context:\n# https://bazelbuild.slack.com/archives/C014RARENH0/p1691158021917459?thread_ts=1691156601.420349&cid=C014RARENH0\ncommon:ruleset --check_direct_dependencies=\"off\"\n# Docs: https://registry.build/flag/bazel?filter=check_direct_dependencies\n\n# On CI, use colors to highlight output on the screen. Set to `no` if your CI does not display colors.\ncommon:ci --color=\"yes\"\n# Docs: https://registry.build/flag/bazel?filter=color\n\n# On CI, use cursor controls in screen output.\ncommon:ci --curses=\"yes\"\n# Docs: https://registry.build/flag/bazel?filter=curses\n\n# Bazel picks up host-OS-specific config lines from bazelrc files. For example, if the host OS is\n# Linux and you run bazel build, Bazel picks up lines starting with build:linux. Supported OS\n# identifiers are `linux`, `macos`, `windows`, `freebsd`, and `openbsd`. Enabling this flag is\n# equivalent to using `--config=linux` on Linux, `--config=windows` on Windows, etc.\ncommon --enable_platform_specific_config\n# Docs: https://registry.build/flag/bazel?filter=enable_platform_specific_config\n\n# Speed up all builds by not checking if external repository files have been modified.\n# For reference: https://github.com/bazelbuild/bazel/blob/1af61b21df99edc2fc66939cdf14449c2661f873/src/main/java/com/google/devtools/build/lib/bazel/repository/RepositoryOptions.java#L244\ncommon --noexperimental_check_external_repository_files\n# Docs: https://registry.build/flag/bazel?filter=experimental_check_external_repository_files\n\n# Always download coverage files for tests from the remote cache. By default, coverage files are not\n# downloaded on test result cache hits when --remote_download_minimal is enabled, making it impossible\n# to generate a full coverage report.\ncommon --experimental_fetch_all_coverage_outputs\n# Docs: https://registry.build/flag/bazel?filter=experimental_fetch_all_coverage_outputs\n\n# This flag was added in Bazel 6.2.0 with a default of zero:\n# https://github.com/bazelbuild/bazel/commit/24b45890c431de98d586fdfe5777031612049135\n# For Bazel 8.0.0rc1 the default was changed to 5:\n# https://github.com/bazelbuild/bazel/commit/739e37de66f4913bec1a55b2f2a162e7db6f2d0f\n# Back-port the updated flag default value to older Bazel versions.\ncommon --experimental_remote_cache_eviction_retries=5\n# Docs: https://registry.build/flag/bazel?filter=experimental_remote_cache_eviction_retries\n\n# This flag was added in Bazel 5.0.0 with a default of zero:\n# https://github.com/bazelbuild/bazel/commit/a1137ec1338d9549fd34a9a74502ffa58c286a8e\n# For Bazel 8.0.0 the default was changed to 5:\n# https://github.com/bazelbuild/bazel/commit/9335cf989ee6a678ca10bc4da72214634cef0a57\n# Back-port the updated flag default value to older Bazel versions.\ncommon --experimental_repository_downloader_retries=5\n# Docs: https://registry.build/flag/bazel?filter=experimental_repository_downloader_retries\n\n# Set this flag to enable re-tries of failed tests on CI.\n# When any test target fails, try one or more times. This applies regardless of whether the \"flaky\"\n# tag appears on the target definition.\n# This is a tradeoff: legitimately failing tests will take longer to report,\n# but we can \"paper over\" flaky tests that pass most of the time.\n#\n# An alternative is to mark every flaky test with the `flaky = True` attribute, but this requires\n# the buildcop to make frequent code edits.\n# This flag is not recommended for local builds: flakiness is more likely to get fixed if it is\n# observed during development.\n#\n# Note that when passing after the first attempt, Bazel will give a special \"FLAKY\" status rather than \"PASSED\".\ntest:ci --flaky_test_attempts=2\n# Docs: https://registry.build/flag/bazel?filter=flaky_test_attempts\n\n# Fixes builds hanging on CI that get the TCP connection closed without sending RST packets.\ncommon:ci --grpc_keepalive_time=\"30s\"\n# Docs: https://registry.build/flag/bazel?filter=grpc_keepalive_time\n\n# Output a heap dump if an OOM is thrown during a Bazel invocation\n# (including OOMs due to `--experimental_oom_more_eagerly_threshold`).\n# The dump will be written to `<output_base>/<invocation_id>.heapdump.hprof`.\n# You should configure CI to upload this artifact for later inspection.\ncommon --heap_dump_on_oom\n# Docs: https://registry.build/flag/bazel?filter=heap_dump_on_oom\n\n# Allow the Bazel server to check directory sources for changes. Ensures that the Bazel server\n# notices when a directory changes, if you have a directory listed in the srcs of some target.\n# Recommended when using [copy_directory](https://github.com/bazel-contrib/bazel-lib/blob/main/docs/copy_directory.md)\n# and [rules_js](https://github.com/aspect-build/rules_js) since npm package are source directories inputs to copy_directory actions.\nstartup --host_jvm_args=\"-DBAZEL_TRACK_SOURCE_DIRECTORIES=1\"\n# Docs: https://registry.build/flag/bazel?filter=host_jvm_args\n\n# By default, Bazel automatically creates __init__.py files for py_binary and py_test targets.\n# From https://github.com/bazelbuild/bazel/issues/10076:\n# > It is magic at a distance.\n# > Python programmers are already used to creating __init__.py files in their source trees,\n# > so doing it behind their backs introduces confusion and changes the semantics of imports\ncommon --incompatible_default_to_explicit_init_py\n# Docs: https://registry.build/flag/bazel?filter=incompatible_default_to_explicit_init_py\n\n# Disallow empty glob patterns.\n# The glob() function tends to be error-prone, because any typo in a path will silently return an empty list.\n# This flag was added in Bazel 0.27 and flipped in Bazel 8: https://github.com/bazelbuild/bazel/issues/8195\ncommon --incompatible_disallow_empty_glob\n# Docs: https://registry.build/flag/bazel?filter=incompatible_disallow_empty_glob\n\n# Accept multiple --modify_execution_info flags, rather than the last flag overwriting earlier ones.\ncommon --incompatible_modify_execution_info_additive\n# Docs: https://registry.build/flag/bazel?filter=incompatible_modify_execution_info_additive\n\n# Make builds more reproducible by using a static value for PATH and not inheriting LD_LIBRARY_PATH.\n# Use `--action_env=ENV_VARIABLE` if you want to inherit specific variables from the environment where Bazel runs.\n# Note that doing so can prevent cross-user caching if a shared cache is used.\n# See https://github.com/bazelbuild/bazel/issues/2574 for more details.\ncommon --incompatible_strict_action_env\n# Docs: https://registry.build/flag/bazel?filter=incompatible_strict_action_env\n\n# Performance improvement: avoid laying out a second copy of the runfiles tree.\n# See https://github.com/bazelbuild/bazel/issues/23574.\n# This flag was flipped for Bazel 8.\ncommon --nolegacy_external_runfiles\n# Docs: https://registry.build/flag/bazel?filter=legacy_external_runfiles\n\n# On CI, don't download remote outputs to the local machine.\n# Most CI pipelines don't need to access the files and they can remain at rest on the remote cache.\n# Significant time can be spent on needless downloads, which is especially noticeable on fully-cached builds.\n#\n# If you do need to download files, the fastest options are:\n# - (preferred) Use `remote_download_regex` to specify the files to download.\n# - Use the Remote Output Service (https://blog.bazel.build/2024/07/23/remote-output-service.html)\n# to lazy-materialize specific files after the build completes.\n# - Perform a second bazel command with specific targets and override this flag with the `toplevel` value.\n# - To copy executable targets, you can use `bazel run --run_under=cp //some:binary_target <destination path>`.\ncommon:ci --remote_download_outputs=\"minimal\"\n# Docs: https://registry.build/flag/bazel?filter=remote_download_outputs\n\n# On CI, fall back to standalone local execution strategy if remote execution fails.\n# Otherwise, when a grpc remote cache connection fails, it would fail the build.\ncommon:ci --remote_local_fallback\n# Docs: https://registry.build/flag/bazel?filter=remote_local_fallback\n\n# On CI, extend the maximum amount of time to wait for remote execution and cache calls.\ncommon:ci --remote_timeout=3600\n# Docs: https://registry.build/flag/bazel?filter=remote_timeout\n\n# Do not upload locally executed action results to the remote cache.\n# This should be the default for local builds so local builds cannot poison the remote cache.\n#\n# Note that this flag is flipped to True under --config=ci, see below.\ncommon --noremote_upload_local_results\n# Docs: https://registry.build/flag/bazel?filter=remote_upload_local_results\n\n# On CI, upload locally executed action results to the remote cache.\ncommon:ci --remote_upload_local_results\n# Docs: https://registry.build/flag/bazel?filter=remote_upload_local_results\n\n# Repository rules, such as rules_jvm_external: put Bazel's JDK on the path.\n# Avoids non-hermeticity from dependency on a JAVA_HOME pointing at a system JDK\n# see https://github.com/bazelbuild/rules_jvm_external/issues/445\ncommon --repo_env=\"JAVA_HOME=../bazel_tools/jdk\"\n# Docs: https://registry.build/flag/bazel?filter=repo_env\n\n# Reuse sandbox directories between invocations.\n# Directories used by sandboxed non-worker execution may be reused to avoid unnecessary setup costs.\n# Saves time on sandbox creation and deletion when many of the same kind of action is spawned during the build.\ncommon --reuse_sandbox_directories\n# Docs: https://registry.build/flag/bazel?filter=reuse_sandbox_directories\n\n# Don't allow network access for build actions in the sandbox by default.\n# Avoids accidental non-hermeticity in actions/tests which depend on remote services.\n# Developers should tag targets with `tags=[\"requires-network\"]` to be explicit that they need network access.\n# Note that the sandbox cannot print a message to the console if it denies network access,\n# so failures under this flag appear as application errors in the networking layer.\ncommon --nosandbox_default_allow_network\n# Docs: https://registry.build/flag/bazel?filter=sandbox_default_allow_network\n\n# Only show progress every 60 seconds on CI.\n# We want to find a compromise between printing often enough to show that the build isn't stuck,\n# but not so often that we produce a long log file that requires a lot of scrolling.\ncommon:ci --show_progress_rate_limit=60\n# Docs: https://registry.build/flag/bazel?filter=show_progress_rate_limit\n\n# The printed files are convenient strings for copy+pasting to the shell, to execute them.\n# This option requires an integer argument, which is the threshold number of targets above which result information is not printed.\n# Show the output files created by builds that requested more than one target.\n# This helps users locate the build outputs in more cases.\ncommon --show_result=20\n# Docs: https://registry.build/flag/bazel?filter=show_result\n\n# On CI, add a timestamp to each message generated by Bazel specifying the time at which the message was displayed.\n# This makes it easier to reason about what were the slowest steps on CI.\ncommon:ci --show_timestamps\n# Docs: https://registry.build/flag/bazel?filter=show_timestamps\n\n# The terminal width in columns. Configure this to override the default value based on what your CI system renders.\ncommon:ci --terminal_columns=143\n# Docs: https://registry.build/flag/bazel?filter=terminal_columns\n\n# Output test errors to stderr so users don't have to `cat` or open test failure log files when test fail.\n# This makes the log noisier in exchange for reducing the time-to-feedback on test failures for users.\ncommon --test_output=\"errors\"\n# Docs: https://registry.build/flag/bazel?filter=test_output\n\n# Stream stdout/stderr output from each test in real-time.\n# This provides immediate feedback during test execution, useful for debugging test failures.\ncommon:debug --test_output=\"streamed\"\n# Docs: https://registry.build/flag/bazel?filter=test_output\n\n# Run one test at a time in exclusive mode.\n# This prevents test interference and provides clearer output when debugging test issues.\ncommon:debug --test_strategy=\"exclusive\"\n# Docs: https://registry.build/flag/bazel?filter=test_strategy\n\n# The default test_summary (\"short\") prints a result for every test target that was executed.\n# In a large repo this amounts to hundreds of lines of additional log output when testing a broad wildcard pattern like //...\n# This value means to print information only about unsuccessful tests that were run.\ntest:ci --test_summary=\"terse\"\n# Docs: https://registry.build/flag/bazel?filter=test_summary\n\n# Prevent long running tests from timing out.\n# Set to a high value to allow tests to complete even if they take longer than expected.\ncommon:debug --test_timeout=9999\n# Docs: https://registry.build/flag/bazel?filter=test_timeout\n"
  },
  {
    "path": "tools/preset8.bazelrc",
    "content": "# Generated by bazelrc-preset.bzl\n# To update this file, run:\n#   bazel run @@//tools:preset.update\n\n# On CI, announce all announces command options read from the bazelrc file(s) when starting up at the\n# beginning of each Bazel invocation. This is very useful on CI to be able to inspect which flags\n# are being applied on each run based on the order of overrides.\ncommon:ci --announce_rc\n# Docs: https://registry.build/flag/bazel?filter=announce_rc\n\n# Avoid creating a runfiles tree for binaries or tests until it is needed.\n# See https://github.com/bazelbuild/bazel/issues/6627\n# This may break local workflows that `build` a binary target, then run the resulting program outside of `bazel run`.\n# In those cases, the script will need to call `bazel build --build_runfile_links //my/binary:target` and then execute the resulting program.\ncommon --nobuild_runfile_links\n# Docs: https://registry.build/flag/bazel?filter=build_runfile_links\n\n# Always run tests even if they have cached results.\n# This ensures tests are executed fresh each time, useful for debugging and ensuring test reliability.\ncommon:debug --nocache_test_results\n# Docs: https://registry.build/flag/bazel?filter=cache_test_results\n\n# Don’t encourage a rules author to update their deps if not needed.\n# These bazel_dep calls should indicate the minimum version constraint of the ruleset.\n# If the author instead updates to the newest of any of their transitives, as this flag would suggest,\n# then they'll also force their dependents to a newer version.\n# Context:\n# https://bazelbuild.slack.com/archives/C014RARENH0/p1691158021917459?thread_ts=1691156601.420349&cid=C014RARENH0\ncommon:ruleset --check_direct_dependencies=\"off\"\n# Docs: https://registry.build/flag/bazel?filter=check_direct_dependencies\n\n# On CI, use colors to highlight output on the screen. Set to `no` if your CI does not display colors.\ncommon:ci --color=\"yes\"\n# Docs: https://registry.build/flag/bazel?filter=color\n\n# On CI, use cursor controls in screen output.\ncommon:ci --curses=\"yes\"\n# Docs: https://registry.build/flag/bazel?filter=curses\n\n# Bazel picks up host-OS-specific config lines from bazelrc files. For example, if the host OS is\n# Linux and you run bazel build, Bazel picks up lines starting with build:linux. Supported OS\n# identifiers are `linux`, `macos`, `windows`, `freebsd`, and `openbsd`. Enabling this flag is\n# equivalent to using `--config=linux` on Linux, `--config=windows` on Windows, etc.\ncommon --enable_platform_specific_config\n# Docs: https://registry.build/flag/bazel?filter=enable_platform_specific_config\n\n# Speed up all builds by not checking if external repository files have been modified.\n# For reference: https://github.com/bazelbuild/bazel/blob/1af61b21df99edc2fc66939cdf14449c2661f873/src/main/java/com/google/devtools/build/lib/bazel/repository/RepositoryOptions.java#L244\ncommon --noexperimental_check_external_repository_files\n# Docs: https://registry.build/flag/bazel?filter=experimental_check_external_repository_files\n\n# Always download coverage files for tests from the remote cache. By default, coverage files are not\n# downloaded on test result cache hits when --remote_download_minimal is enabled, making it impossible\n# to generate a full coverage report.\ncommon --experimental_fetch_all_coverage_outputs\n# Docs: https://registry.build/flag/bazel?filter=experimental_fetch_all_coverage_outputs\n\n# Set this flag to enable re-tries of failed tests on CI.\n# When any test target fails, try one or more times. This applies regardless of whether the \"flaky\"\n# tag appears on the target definition.\n# This is a tradeoff: legitimately failing tests will take longer to report,\n# but we can \"paper over\" flaky tests that pass most of the time.\n#\n# An alternative is to mark every flaky test with the `flaky = True` attribute, but this requires\n# the buildcop to make frequent code edits.\n# This flag is not recommended for local builds: flakiness is more likely to get fixed if it is\n# observed during development.\n#\n# Note that when passing after the first attempt, Bazel will give a special \"FLAKY\" status rather than \"PASSED\".\ntest:ci --flaky_test_attempts=2\n# Docs: https://registry.build/flag/bazel?filter=flaky_test_attempts\n\n# Fixes builds hanging on CI that get the TCP connection closed without sending RST packets.\ncommon:ci --grpc_keepalive_time=\"30s\"\n# Docs: https://registry.build/flag/bazel?filter=grpc_keepalive_time\n\n# Output a heap dump if an OOM is thrown during a Bazel invocation\n# (including OOMs due to `--experimental_oom_more_eagerly_threshold`).\n# The dump will be written to `<output_base>/<invocation_id>.heapdump.hprof`.\n# You should configure CI to upload this artifact for later inspection.\ncommon --heap_dump_on_oom\n# Docs: https://registry.build/flag/bazel?filter=heap_dump_on_oom\n\n# Allow the Bazel server to check directory sources for changes. Ensures that the Bazel server\n# notices when a directory changes, if you have a directory listed in the srcs of some target.\n# Recommended when using [copy_directory](https://github.com/bazel-contrib/bazel-lib/blob/main/docs/copy_directory.md)\n# and [rules_js](https://github.com/aspect-build/rules_js) since npm package are source directories inputs to copy_directory actions.\nstartup --host_jvm_args=\"-DBAZEL_TRACK_SOURCE_DIRECTORIES=1\"\n# Docs: https://registry.build/flag/bazel?filter=host_jvm_args\n\n# By default, Bazel automatically creates __init__.py files for py_binary and py_test targets.\n# From https://github.com/bazelbuild/bazel/issues/10076:\n# > It is magic at a distance.\n# > Python programmers are already used to creating __init__.py files in their source trees,\n# > so doing it behind their backs introduces confusion and changes the semantics of imports\ncommon --incompatible_default_to_explicit_init_py\n# Docs: https://registry.build/flag/bazel?filter=incompatible_default_to_explicit_init_py\n\n# Fail if Starlark files are not UTF-8 encoded.\n# Introduced in Bazel 8.1, see https://github.com/bazelbuild/bazel/pull/24944\ncommon --incompatible_enforce_starlark_utf8=\"error\"\n# Docs: https://registry.build/flag/bazel?filter=incompatible_enforce_starlark_utf8\n\n# Accept multiple --modify_execution_info flags, rather than the last flag overwriting earlier ones.\ncommon --incompatible_modify_execution_info_additive\n# Docs: https://registry.build/flag/bazel?filter=incompatible_modify_execution_info_additive\n\n# Make builds more reproducible by using a static value for PATH and not inheriting LD_LIBRARY_PATH.\n# Use `--action_env=ENV_VARIABLE` if you want to inherit specific variables from the environment where Bazel runs.\n# Note that doing so can prevent cross-user caching if a shared cache is used.\n# See https://github.com/bazelbuild/bazel/issues/2574 for more details.\ncommon --incompatible_strict_action_env\n# Docs: https://registry.build/flag/bazel?filter=incompatible_strict_action_env\n\n# Add the CloudFlare mirror of BCR-referenced downloads.\n# Improves reliability of Bazel when CDNs are flaky, for example issues with ftp.gnu.org in 2025.\ncommon --module_mirrors=\"https://bcr.cloudflaremirrors.com\"\n# Docs: https://registry.build/flag/bazel?filter=module_mirrors\n\n# On CI, don't download remote outputs to the local machine.\n# Most CI pipelines don't need to access the files and they can remain at rest on the remote cache.\n# Significant time can be spent on needless downloads, which is especially noticeable on fully-cached builds.\n#\n# If you do need to download files, the fastest options are:\n# - (preferred) Use `remote_download_regex` to specify the files to download.\n# - Use the Remote Output Service (https://blog.bazel.build/2024/07/23/remote-output-service.html)\n# to lazy-materialize specific files after the build completes.\n# - Perform a second bazel command with specific targets and override this flag with the `toplevel` value.\n# - To copy executable targets, you can use `bazel run --run_under=cp //some:binary_target <destination path>`.\ncommon:ci --remote_download_outputs=\"minimal\"\n# Docs: https://registry.build/flag/bazel?filter=remote_download_outputs\n\n# On CI, fall back to standalone local execution strategy if remote execution fails.\n# Otherwise, when a grpc remote cache connection fails, it would fail the build.\ncommon:ci --remote_local_fallback\n# Docs: https://registry.build/flag/bazel?filter=remote_local_fallback\n\n# On CI, extend the maximum amount of time to wait for remote execution and cache calls.\ncommon:ci --remote_timeout=3600\n# Docs: https://registry.build/flag/bazel?filter=remote_timeout\n\n# Do not upload locally executed action results to the remote cache.\n# This should be the default for local builds so local builds cannot poison the remote cache.\n#\n# Note that this flag is flipped to True under --config=ci, see below.\ncommon --noremote_upload_local_results\n# Docs: https://registry.build/flag/bazel?filter=remote_upload_local_results\n\n# On CI, upload locally executed action results to the remote cache.\ncommon:ci --remote_upload_local_results\n# Docs: https://registry.build/flag/bazel?filter=remote_upload_local_results\n\n# Repository rules, such as rules_jvm_external: put Bazel's JDK on the path.\n# Avoids non-hermeticity from dependency on a JAVA_HOME pointing at a system JDK\n# see https://github.com/bazelbuild/rules_jvm_external/issues/445\ncommon --repo_env=\"JAVA_HOME=../bazel_tools/jdk\"\n# Docs: https://registry.build/flag/bazel?filter=repo_env\n\n# Reuse sandbox directories between invocations.\n# Directories used by sandboxed non-worker execution may be reused to avoid unnecessary setup costs.\n# Saves time on sandbox creation and deletion when many of the same kind of action is spawned during the build.\ncommon --reuse_sandbox_directories\n# Docs: https://registry.build/flag/bazel?filter=reuse_sandbox_directories\n\n# Don't allow network access for build actions in the sandbox by default.\n# Avoids accidental non-hermeticity in actions/tests which depend on remote services.\n# Developers should tag targets with `tags=[\"requires-network\"]` to be explicit that they need network access.\n# Note that the sandbox cannot print a message to the console if it denies network access,\n# so failures under this flag appear as application errors in the networking layer.\ncommon --nosandbox_default_allow_network\n# Docs: https://registry.build/flag/bazel?filter=sandbox_default_allow_network\n\n# Only show progress every 60 seconds on CI.\n# We want to find a compromise between printing often enough to show that the build isn't stuck,\n# but not so often that we produce a long log file that requires a lot of scrolling.\ncommon:ci --show_progress_rate_limit=60\n# Docs: https://registry.build/flag/bazel?filter=show_progress_rate_limit\n\n# The printed files are convenient strings for copy+pasting to the shell, to execute them.\n# This option requires an integer argument, which is the threshold number of targets above which result information is not printed.\n# Show the output files created by builds that requested more than one target.\n# This helps users locate the build outputs in more cases.\ncommon --show_result=20\n# Docs: https://registry.build/flag/bazel?filter=show_result\n\n# On CI, add a timestamp to each message generated by Bazel specifying the time at which the message was displayed.\n# This makes it easier to reason about what were the slowest steps on CI.\ncommon:ci --show_timestamps\n# Docs: https://registry.build/flag/bazel?filter=show_timestamps\n\n# The terminal width in columns. Configure this to override the default value based on what your CI system renders.\ncommon:ci --terminal_columns=143\n# Docs: https://registry.build/flag/bazel?filter=terminal_columns\n\n# Output test errors to stderr so users don't have to `cat` or open test failure log files when test fail.\n# This makes the log noisier in exchange for reducing the time-to-feedback on test failures for users.\ncommon --test_output=\"errors\"\n# Docs: https://registry.build/flag/bazel?filter=test_output\n\n# Stream stdout/stderr output from each test in real-time.\n# This provides immediate feedback during test execution, useful for debugging test failures.\ncommon:debug --test_output=\"streamed\"\n# Docs: https://registry.build/flag/bazel?filter=test_output\n\n# Run one test at a time in exclusive mode.\n# This prevents test interference and provides clearer output when debugging test issues.\ncommon:debug --test_strategy=\"exclusive\"\n# Docs: https://registry.build/flag/bazel?filter=test_strategy\n\n# The default test_summary (\"short\") prints a result for every test target that was executed.\n# In a large repo this amounts to hundreds of lines of additional log output when testing a broad wildcard pattern like //...\n# This value means to print information only about unsuccessful tests that were run.\ntest:ci --test_summary=\"terse\"\n# Docs: https://registry.build/flag/bazel?filter=test_summary\n\n# Prevent long running tests from timing out.\n# Set to a high value to allow tests to complete even if they take longer than expected.\ncommon:debug --test_timeout=9999\n# Docs: https://registry.build/flag/bazel?filter=test_timeout\n"
  },
  {
    "path": "tools/preset9.bazelrc",
    "content": "# Generated by bazelrc-preset.bzl\n# To update this file, run:\n#   bazel run @@//tools:preset.update\n\n# On CI, announce all announces command options read from the bazelrc file(s) when starting up at the\n# beginning of each Bazel invocation. This is very useful on CI to be able to inspect which flags\n# are being applied on each run based on the order of overrides.\ncommon:ci --announce_rc\n# Docs: https://registry.build/flag/bazel?filter=announce_rc\n\n# Avoid creating a runfiles tree for binaries or tests until it is needed.\n# See https://github.com/bazelbuild/bazel/issues/6627\n# This may break local workflows that `build` a binary target, then run the resulting program outside of `bazel run`.\n# In those cases, the script will need to call `bazel build --build_runfile_links //my/binary:target` and then execute the resulting program.\ncommon --nobuild_runfile_links\n# Docs: https://registry.build/flag/bazel?filter=build_runfile_links\n\n# Always run tests even if they have cached results.\n# This ensures tests are executed fresh each time, useful for debugging and ensuring test reliability.\ncommon:debug --nocache_test_results\n# Docs: https://registry.build/flag/bazel?filter=cache_test_results\n\n# Don’t encourage a rules author to update their deps if not needed.\n# These bazel_dep calls should indicate the minimum version constraint of the ruleset.\n# If the author instead updates to the newest of any of their transitives, as this flag would suggest,\n# then they'll also force their dependents to a newer version.\n# Context:\n# https://bazelbuild.slack.com/archives/C014RARENH0/p1691158021917459?thread_ts=1691156601.420349&cid=C014RARENH0\ncommon:ruleset --check_direct_dependencies=\"off\"\n# Docs: https://registry.build/flag/bazel?filter=check_direct_dependencies\n\n# On CI, use colors to highlight output on the screen. Set to `no` if your CI does not display colors.\ncommon:ci --color=\"yes\"\n# Docs: https://registry.build/flag/bazel?filter=color\n\n# On CI, use cursor controls in screen output.\ncommon:ci --curses=\"yes\"\n# Docs: https://registry.build/flag/bazel?filter=curses\n\n# Bazel picks up host-OS-specific config lines from bazelrc files. For example, if the host OS is\n# Linux and you run bazel build, Bazel picks up lines starting with build:linux. Supported OS\n# identifiers are `linux`, `macos`, `windows`, `freebsd`, and `openbsd`. Enabling this flag is\n# equivalent to using `--config=linux` on Linux, `--config=windows` on Windows, etc.\ncommon --enable_platform_specific_config\n# Docs: https://registry.build/flag/bazel?filter=enable_platform_specific_config\n\n# Speed up all builds by not checking if external repository files have been modified.\n# For reference: https://github.com/bazelbuild/bazel/blob/1af61b21df99edc2fc66939cdf14449c2661f873/src/main/java/com/google/devtools/build/lib/bazel/repository/RepositoryOptions.java#L244\ncommon --noexperimental_check_external_repository_files\n# Docs: https://registry.build/flag/bazel?filter=experimental_check_external_repository_files\n\n# Always download coverage files for tests from the remote cache. By default, coverage files are not\n# downloaded on test result cache hits when --remote_download_minimal is enabled, making it impossible\n# to generate a full coverage report.\ncommon --experimental_fetch_all_coverage_outputs\n# Docs: https://registry.build/flag/bazel?filter=experimental_fetch_all_coverage_outputs\n\n# Set this flag to enable re-tries of failed tests on CI.\n# When any test target fails, try one or more times. This applies regardless of whether the \"flaky\"\n# tag appears on the target definition.\n# This is a tradeoff: legitimately failing tests will take longer to report,\n# but we can \"paper over\" flaky tests that pass most of the time.\n#\n# An alternative is to mark every flaky test with the `flaky = True` attribute, but this requires\n# the buildcop to make frequent code edits.\n# This flag is not recommended for local builds: flakiness is more likely to get fixed if it is\n# observed during development.\n#\n# Note that when passing after the first attempt, Bazel will give a special \"FLAKY\" status rather than \"PASSED\".\ntest:ci --flaky_test_attempts=2\n# Docs: https://registry.build/flag/bazel?filter=flaky_test_attempts\n\n# Fixes builds hanging on CI that get the TCP connection closed without sending RST packets.\ncommon:ci --grpc_keepalive_time=\"30s\"\n# Docs: https://registry.build/flag/bazel?filter=grpc_keepalive_time\n\n# Output a heap dump if an OOM is thrown during a Bazel invocation\n# (including OOMs due to `--experimental_oom_more_eagerly_threshold`).\n# The dump will be written to `<output_base>/<invocation_id>.heapdump.hprof`.\n# You should configure CI to upload this artifact for later inspection.\ncommon --heap_dump_on_oom\n# Docs: https://registry.build/flag/bazel?filter=heap_dump_on_oom\n\n# Allow the Bazel server to check directory sources for changes. Ensures that the Bazel server\n# notices when a directory changes, if you have a directory listed in the srcs of some target.\n# Recommended when using [copy_directory](https://github.com/bazel-contrib/bazel-lib/blob/main/docs/copy_directory.md)\n# and [rules_js](https://github.com/aspect-build/rules_js) since npm package are source directories inputs to copy_directory actions.\nstartup --host_jvm_args=\"-DBAZEL_TRACK_SOURCE_DIRECTORIES=1\"\n# Docs: https://registry.build/flag/bazel?filter=host_jvm_args\n\n# By default, Bazel automatically creates __init__.py files for py_binary and py_test targets.\n# From https://github.com/bazelbuild/bazel/issues/10076:\n# > It is magic at a distance.\n# > Python programmers are already used to creating __init__.py files in their source trees,\n# > so doing it behind their backs introduces confusion and changes the semantics of imports\ncommon --incompatible_default_to_explicit_init_py\n# Docs: https://registry.build/flag/bazel?filter=incompatible_default_to_explicit_init_py\n\n# Fail if Starlark files are not UTF-8 encoded.\n# Introduced in Bazel 8.1, see https://github.com/bazelbuild/bazel/pull/24944\ncommon --incompatible_enforce_starlark_utf8=\"error\"\n# Docs: https://registry.build/flag/bazel?filter=incompatible_enforce_starlark_utf8\n\n# Accept multiple --modify_execution_info flags, rather than the last flag overwriting earlier ones.\ncommon --incompatible_modify_execution_info_additive\n# Docs: https://registry.build/flag/bazel?filter=incompatible_modify_execution_info_additive\n\n# Make builds more reproducible by using a static value for PATH and not inheriting LD_LIBRARY_PATH.\n# Use `--action_env=ENV_VARIABLE` if you want to inherit specific variables from the environment where Bazel runs.\n# Note that doing so can prevent cross-user caching if a shared cache is used.\n# See https://github.com/bazelbuild/bazel/issues/2574 for more details.\ncommon --incompatible_strict_action_env\n# Docs: https://registry.build/flag/bazel?filter=incompatible_strict_action_env\n\n# Add the CloudFlare mirror of BCR-referenced downloads.\n# Improves reliability of Bazel when CDNs are flaky, for example issues with ftp.gnu.org in 2025.\ncommon --module_mirrors=\"https://bcr.cloudflaremirrors.com\"\n# Docs: https://registry.build/flag/bazel?filter=module_mirrors\n\n# On CI, don't download remote outputs to the local machine.\n# Most CI pipelines don't need to access the files and they can remain at rest on the remote cache.\n# Significant time can be spent on needless downloads, which is especially noticeable on fully-cached builds.\n#\n# If you do need to download files, the fastest options are:\n# - (preferred) Use `remote_download_regex` to specify the files to download.\n# - Use the Remote Output Service (https://blog.bazel.build/2024/07/23/remote-output-service.html)\n# to lazy-materialize specific files after the build completes.\n# - Perform a second bazel command with specific targets and override this flag with the `toplevel` value.\n# - To copy executable targets, you can use `bazel run --run_under=cp //some:binary_target <destination path>`.\ncommon:ci --remote_download_outputs=\"minimal\"\n# Docs: https://registry.build/flag/bazel?filter=remote_download_outputs\n\n# On CI, fall back to standalone local execution strategy if remote execution fails.\n# Otherwise, when a grpc remote cache connection fails, it would fail the build.\ncommon:ci --remote_local_fallback\n# Docs: https://registry.build/flag/bazel?filter=remote_local_fallback\n\n# On CI, extend the maximum amount of time to wait for remote execution and cache calls.\ncommon:ci --remote_timeout=3600\n# Docs: https://registry.build/flag/bazel?filter=remote_timeout\n\n# Do not upload locally executed action results to the remote cache.\n# This should be the default for local builds so local builds cannot poison the remote cache.\n#\n# Note that this flag is flipped to True under --config=ci, see below.\ncommon --noremote_upload_local_results\n# Docs: https://registry.build/flag/bazel?filter=remote_upload_local_results\n\n# On CI, upload locally executed action results to the remote cache.\ncommon:ci --remote_upload_local_results\n# Docs: https://registry.build/flag/bazel?filter=remote_upload_local_results\n\n# Repository rules, such as rules_jvm_external: put Bazel's JDK on the path.\n# Avoids non-hermeticity from dependency on a JAVA_HOME pointing at a system JDK\n# see https://github.com/bazelbuild/rules_jvm_external/issues/445\ncommon --repo_env=\"JAVA_HOME=../bazel_tools/jdk\"\n# Docs: https://registry.build/flag/bazel?filter=repo_env\n\n# Reuse sandbox directories between invocations.\n# Directories used by sandboxed non-worker execution may be reused to avoid unnecessary setup costs.\n# Saves time on sandbox creation and deletion when many of the same kind of action is spawned during the build.\ncommon --reuse_sandbox_directories\n# Docs: https://registry.build/flag/bazel?filter=reuse_sandbox_directories\n\n# Don't allow network access for build actions in the sandbox by default.\n# Avoids accidental non-hermeticity in actions/tests which depend on remote services.\n# Developers should tag targets with `tags=[\"requires-network\"]` to be explicit that they need network access.\n# Note that the sandbox cannot print a message to the console if it denies network access,\n# so failures under this flag appear as application errors in the networking layer.\ncommon --nosandbox_default_allow_network\n# Docs: https://registry.build/flag/bazel?filter=sandbox_default_allow_network\n\n# Only show progress every 60 seconds on CI.\n# We want to find a compromise between printing often enough to show that the build isn't stuck,\n# but not so often that we produce a long log file that requires a lot of scrolling.\ncommon:ci --show_progress_rate_limit=60\n# Docs: https://registry.build/flag/bazel?filter=show_progress_rate_limit\n\n# The printed files are convenient strings for copy+pasting to the shell, to execute them.\n# This option requires an integer argument, which is the threshold number of targets above which result information is not printed.\n# Show the output files created by builds that requested more than one target.\n# This helps users locate the build outputs in more cases.\ncommon --show_result=20\n# Docs: https://registry.build/flag/bazel?filter=show_result\n\n# On CI, add a timestamp to each message generated by Bazel specifying the time at which the message was displayed.\n# This makes it easier to reason about what were the slowest steps on CI.\ncommon:ci --show_timestamps\n# Docs: https://registry.build/flag/bazel?filter=show_timestamps\n\n# The terminal width in columns. Configure this to override the default value based on what your CI system renders.\ncommon:ci --terminal_columns=143\n# Docs: https://registry.build/flag/bazel?filter=terminal_columns\n\n# Output test errors to stderr so users don't have to `cat` or open test failure log files when test fail.\n# This makes the log noisier in exchange for reducing the time-to-feedback on test failures for users.\ncommon --test_output=\"errors\"\n# Docs: https://registry.build/flag/bazel?filter=test_output\n\n# Stream stdout/stderr output from each test in real-time.\n# This provides immediate feedback during test execution, useful for debugging test failures.\ncommon:debug --test_output=\"streamed\"\n# Docs: https://registry.build/flag/bazel?filter=test_output\n\n# Run one test at a time in exclusive mode.\n# This prevents test interference and provides clearer output when debugging test issues.\ncommon:debug --test_strategy=\"exclusive\"\n# Docs: https://registry.build/flag/bazel?filter=test_strategy\n\n# The default test_summary (\"short\") prints a result for every test target that was executed.\n# In a large repo this amounts to hundreds of lines of additional log output when testing a broad wildcard pattern like //...\n# This value means to print information only about unsuccessful tests that were run.\ntest:ci --test_summary=\"terse\"\n# Docs: https://registry.build/flag/bazel?filter=test_summary\n\n# Prevent long running tests from timing out.\n# Set to a high value to allow tests to complete even if they take longer than expected.\ncommon:debug --test_timeout=9999\n# Docs: https://registry.build/flag/bazel?filter=test_timeout\n"
  },
  {
    "path": "tools/util.bzl",
    "content": "\"\"\"Utility functions for golden testing.\"\"\"\n\nload(\"@bazel_lib//lib:write_source_files.bzl\", \"write_source_file\")\n\ndef golden_test(name, in_file, extension = \"\"):\n    write_source_file(\n        name = name,\n        in_file = in_file,\n        out_file = \"goldens/{}.golden{}\".format(in_file, extension),\n        diff_test = True,\n        testonly = True,\n    )\n"
  }
]