[
  {
    "path": ".dockerignore",
    "content": "# exclude everything, then re-include only what the Go build needs\n*\n\n!go.mod\n!go.sum\n!cmd/\n!pkg/\n!internal/\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*.{sh,yaml,md}]\nend_of_line = lf\ncharset = utf-8\ninsert_final_newline = true\ntrim_trailing_whitespace = true\nindent_style = space\nindent_size = 2\nmax_line_length = 120\n\n[justfile]\nend_of_line = lf\ncharset = utf-8\ninsert_final_newline = true\ntrim_trailing_whitespace = true\nindent_style = space\nindent_size = 2\nmax_line_length = 120\n"
  },
  {
    "path": ".github/workflows/build.yaml",
    "content": "name: Build\non:\n  workflow_dispatch:\n  push:\n    paths-ignore:\n      - \"docs/**\"\n      - \"logo/**\"\n      - \"examples/**\"\n      - \"README.md\"\n      - \"charts/opa-kube-mgmt/README.md\"\n    branches:\n      - \"master\"\n  pull_request:\n    branches:\n      - \"master\"\n      - \"feat/*\"\n      - \"fix/*\"\njobs:\n  build_job:\n    name: Build\n    runs-on: ubuntu-latest\n    permissions:\n      checks: write\n    steps:\n      - uses: actions/checkout@v6\n      - uses: actions/cache@v5\n        with:\n          path: |\n            ~/go/pkg/mod\n            ~/go/bin\n            ~/.local/share/helm/plugins\n          key: go-tools-${{ hashFiles('devbox.json', 'go.mod', 'go.sum') }}\n          restore-keys: |\n            go-tools-\n      - uses: jetify-com/devbox-install-action@v0.15.0\n        with:\n          enable-cache: true\n      - name: lint and unit test\n        run: devbox run -- just test\n      - name: publish helm lint report\n        uses: mikepenz/action-junit-report@v6\n        if: always()\n        with:\n          report_paths: \"build/test-results/helm-unittest/lint.xml\"\n          check_name: \"Helm Lint Tests\"\n          fail_on_failure: true\n          detailed_summary: true\n          include_passed: true\n      - name: publish helm unit test report\n        uses: mikepenz/action-junit-report@v6\n        if: always()\n        with:\n          report_paths: \"build/test-results/helm-unittest/unit.xml\"\n          check_name: \"Helm Unit Tests\"\n          fail_on_failure: true\n          detailed_summary: true\n          include_passed: true\n      - name: e2e test\n        run: devbox run -- just all && devbox run -- just test-e2e-all\n      - name: publish e2e test report\n        uses: mikepenz/action-junit-report@v6\n        if: always()\n        with:\n          report_paths: \"build/test-results/chainsaw/*.xml\"\n          check_name: \"E2E Tests\"\n          fail_on_failure: true\n          detailed_summary: true\n          include_passed: true\n      - name: failure logs\n        if: ${{ failure() }}\n        run: |\n          echo \"---------------------------------------\"\n          kubectl get all\n          echo \"---------------------------------------\"\n          kubectl describe po kube-mgmt-opa-kube-mgmt || true\n          echo \"---------------------------------------\"\n          kubectl logs -l app=kube-mgmt-opa-kube-mgmt -c opa --tail=-1\n          echo \"---------------------------------------\"\n          kubectl logs -l app=kube-mgmt-opa-kube-mgmt -c mgmt --tail=-1\n          echo \"---------------------------------------\"\n"
  },
  {
    "path": ".github/workflows/cache.yaml",
    "content": "name: Update cache\n\non:\n  push:\n    branches:\n      - master\n  workflow_dispatch:\n  schedule:\n    - cron: '0 6 */6 * *'\n\njobs:\n  update_cache:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: actions/cache@v5\n        with:\n          path: |\n            ~/go/pkg/mod\n            ~/go/bin\n            ~/.local/share/helm/plugins\n          key: go-tools-${{ hashFiles('devbox.json', 'go.mod', 'go.sum') }}\n          restore-keys: |\n            go-tools-\n      - uses: jetify-com/devbox-install-action@v0.15.0\n        with:\n          enable-cache: true\n      - run: |\n          eval \"$(devbox shellenv -c . --init-hook)\"\n          devbox run -- go mod download\n"
  },
  {
    "path": ".github/workflows/release.yaml",
    "content": "name: Release\n\npermissions:\n  packages: write\n  contents: write\n\non:\n  workflow_dispatch: {}\n  push:\n    tags:\n      - '[0-9]+.[0-9]+.[0-9]+'\n\njobs:\n  docker_job:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - uses: jetify-com/devbox-install-action@v0.15.0\n        with:\n          enable-cache: true\n      - uses: docker/login-action@v4\n        with:\n          username: ${{ secrets.DOCKER_USER }}\n          password: ${{ secrets.DOCKER_PASSWORD }}\n      - uses: docker/login-action@v4\n        with:\n          registry: ghcr.io\n          username: ${{ github.actor }}\n          password: ${{ github.token }}\n      - name: build and publish image, create chart archive\n        run: devbox run -- devspace build --profile release\n      - name: upload helm artifact\n        uses: actions/upload-artifact@v7\n        with:\n          name: \"helm\"\n          path: \"opa-kube-mgmt-*.tgz\"\n\n  helm_job:\n    runs-on: ubuntu-latest\n    needs: docker_job\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          ref: gh-pages\n      - name: download helm artifact\n        uses: actions/download-artifact@v8\n        id: download\n        with:\n          name: helm\n          path: /tmp/helm\n      - name: update helm index\n        run: |\n          helm repo index /tmp/helm --merge ./charts/index.yaml\n          mv -f /tmp/helm/* ./charts\n      - name: publish index and chart\n        uses: actions-js/push@v1.5\n        with:\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n          branch: gh-pages\n"
  },
  {
    "path": ".gitignore",
    "content": "./kube-mgmt\nbin\n.go\n*.tgz\n.idea\n.vscode/settings.json\n.devspace/\nbuild/\n"
  },
  {
    "path": ".ko.yaml",
    "content": "defaultBaseImage: alpine:3.23.4\nbuilds:\n  - id: kube-mgmt\n    main: ./cmd/kube-mgmt\n    ldflags:\n      - -X github.com/open-policy-agent/kube-mgmt/pkg/version.Version={{.Env.KO_VERSION}}\n      - -X github.com/open-policy-agent/kube-mgmt/pkg/version.Git={{.Env.KO_COMMIT}}\n"
  },
  {
    "path": "LICENSE",
    "content": "\n                                 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 [yyyy] [name of copyright owner]\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"
  },
  {
    "path": "README.md",
    "content": "# ![logo](./logo/logo.png) kube-mgmt\n\n`kube-mgmt` manages policies / data of [Open Policy Agent](https://github.com/open-policy-agent/opa)\ninstances in Kubernetes.\n\nUse `kube-mgmt` to:\n* Load policies and/or static data into OPA instance from `ConfigMap`.\n* Replicate Kubernetes resources\nincluding [CustomResourceDefinitions (CRDs)](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#customresourcedefinitions) into OPA instance.\n\n## Deployment Guide\n\nBoth `OPA` and `kube-mgmt` can be installed using [opa-kube-mgmt](\nhttps://artifacthub.io/packages/helm/opa-kube-mgmt/opa-kube-mgmt) Helm chart.\n\nFollow [README](charts/opa-kube-mgmt/README.md) to install it into K8s cluster.\n\n## Policies and data loading\n\n`kube-mgmt` automatically discovers policies and JSON data\nstored in `ConfigMaps` in Kubernetes and loads them into OPA.\n\n`kube-mgmt` assumes a `ConfigMap` contains policy or JSON data if the `ConfigMap` is:\n\n- Created in a namespace listed in the `--namespaces` option.\n  If you specify `--namespaces=*` then `kube-mgmt` will look for policies in ALL namespaces.\n- Labelled with `openpolicyagent.org/policy=rego` for policies\n- Labelled with `openpolicyagent.org/data=opa` for JSON data\n\nPolicies or data discovery and loading can be disabled using `--enable-policy=false` or `--enable-data=false` flags respectively.\n\nLabel names and their values can be configured using `--policy-label`, `--policy-value`, `--data-label`, `--data-value` CLI options.\n\nWhen a `ConfigMap` has been successfully loaded into OPA,\nthe `openpolicyagent.org/kube-mgmt-status` annotation is set to `{\"status\": \"ok\"}`.\n\nIf loading fails for some reason (e.g., because of a parse error), the\n`openpolicyagent.org/kube-mgmt-status` annotation is set to `{\"status\": \"error\", \"error\": ...}`\nwhere the `error` field contains details about the failure.\n\nData loaded out of ConfigMaps is laid out as follows:\n\n```\n<namespace>/<name>/<key>\n```\n\nFor example, if the following ConfigMap was created:\n\n```yaml\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: hello-data\n  namespace: opa\n  labels:\n    openpolicyagent.org/data: opa\ndata:\n  x.json: |\n    {\"a\": [1,2,3,4]}\n```\nNote: \"x.json\" may be any key.\n\nYou could refer to the data inside your policies as follows:\n\n```rego\ndata.opa[\"hello-data\"][\"x.json\"].a[0]  # evaluates to 1\n```\n\n## K8s resource replication\n\n> [!WARNING]\n> K8s resource replication requires global cluster permission with `ClusterRole` and `ClusterRoleBinding`.\n\n`kube-mgmt` can be configured to replicate Kubernetes resources into OPA so that\nyou can express policies over an eventually consistent cache of Kubernetes\nstate.\n\nReplication is enabled with the following options:\n\n```bash\n# Replicate namespace-level resources. May be specified multiple times.\n--replicate=<[group/]version/resource>\n\n# Replicate cluster-level resources. May be specified multiple times.\n--replicate-cluster=<[group/]version/resource>\n```\n\nBy default resources are replicated from all namespaces.\nUse `--replicate-ignore-namespaces` option to exclude particular namespaces from replication.\n\nKubernetes resources replicated into OPA are laid out as follows:\n\n```\n<replicate-path>/<resource>/<namespace>/<name> # namespace scoped\n<replicate-path>/<resource>/<name>             # cluster scoped\n```\n\n- `<replicate-path>` is configurable (via `--replicate-path`) and\n  defaults to `kubernetes`.\n- `<resource>` is the Kubernetes resource plural, e.g., `nodes`,\n  `pods`, `services`, etc.\n- `<namespace>` is the namespace of the Kubernetes resource.\n- `<name>` is the name of the Kubernetes resource.\n\nFor example, to search for services with the label `\"foo\"` you could write:\n\n```\nsome namespace, name\nservice := data.kubernetes.services[namespace][name]\nservice.metadata.labels[\"foo\"]\n```\n\nAn alternative way to visualize the layout is as single JSON document:\n\n```json\n{\n  \"kubernetes\": {\n    \"services\": {\n      \"default\": {\n        \"example-service\": {...},\n          \"another-service\": {...},\n        }\n      }\n    }\n  }\n}\n```\n\nThe example below would replicate Deployments, Services, and Nodes into OPA:\n\n```bash\n--replicate=apps/v1beta/deployments\n--replicate=v1/services\n--replicate-cluster=v1/nodes\n```\n\nCustom Resource Definitions can also be replicated using the same `--replicate` and `--replicate-cluster` options.\n\n## Admission Control\n\nTo get started with admission control policy enforcement in Kubernetes 1.9 or later see the [Kubernetes Admission Control](http://www.openpolicyagent.org/docs/kubernetes-admission-control.html) tutorial. For older versions of Kubernetes, see [Admission Control (1.7)](./docs/admission-control-1.7.md).\n\nIn the [Kubernetes Admission Control](http://www.openpolicyagent.org/docs/kubernetes-admission-control.html) tutorial, OPA is **NOT** running with an authorization policy configured and hence clients can read and write policies in OPA. When deploying OPA in an insecure environment, it is recommended to configure `authentication` and `authorization` on the OPA daemon. For an example of how OPA can be securely deployed as an admission controller see [Admission Control Secure](./docs/admission-control-secure.md).\n\n## OPA API Endpoints and Least-privilege Configuration\n\n`kube-mgmt` is a privileged component that can load policy and data into OPA.\nOther clients connecting to the OPA API only need to query for policy decisions.\n\nTo load policy and data into OPA, `kube-mgmt` uses the following OPA API\nendpoints:\n\n* `PUT v1/policy/<path>` - upserting policies\n* `DELETE v1/policy/<path>` - deleting policies\n* `PUT v1/data/<path>` - upserting data\n* `PATCH v1/data/<path>` - updating and removing data\n\nMany users configure OPA with a simple API authorization policy that restricts\naccess to the OPA APIs:\n\n```rego\npackage system.authz\n\n# Deny access by default.\ndefault allow = false\n\n# Allow anonymous access to decision `data.example.response`\n#\n# NOTE: the specific decision differs depending on your policies.\n# NOTE: depending on how callers are configured, they may only require this or the default decision below.\nallow {\n  input.path == [\"v0\", \"data\", \"example\", \"response\"]\n  input.method == \"POST\"\n}\n\n# Allow anonymous access to default decision.\nallow {\n  input.path == [\"\"]\n  input.method == \"POST\"\n}\n\n# This is only used for health check in liveness and readiness probe\nallow {\n  input.path == [\"health\"]\n  input.method == \"GET\"\n}\n\n# This is only used for prometheus metrics\nallow {\n  input.path == [\"metrics\"]\n  input.method == \"GET\"\n}\n\n# This is used by kube-mgmt to PUT/PATCH against /v1/data and PUT/DELETE against /v1/policies.\n#\n# NOTE: The $TOKEN value is replaced at deploy-time with the actual value that kube-mgmt will use. This is typically done by an initContainer.\nallow {\n  input.identity == \"$TOKEN\"\n}\n```\n\n## Development\n\n### Environment setup\n\nThis project uses [devbox](https://www.jetify.com/docs/devbox/installing-devbox/) to provide a fully isolated,\nreproducible development environment. All required tools (Go, just, OPA CLI, staticcheck, and others)\nare managed by devbox at pinned versions — no manual installation needed.\n\nTo enter the development shell:\n\n```bash\ndevbox shell\n```\n\nThis project uses `just` as a command runner, configured in [justfile](./justfile).\nRun `just` without arguments to list all available recipes.\n\n### Running the application locally\n\n`kube-mgmt` runs in a local [k3d](https://k3d.io) Kubernetes cluster. Create the cluster once before first use:\n\n```bash\njust all\n```\n\nStart and stop `kube-mgmt` application with:\n\n```bash\njust up\njust down\n```\n\nDelete local k8s cluster\n\n```sh\njust 3d-down\n```\n\n### Tests\n\nThe project has three categories of tests.\n\n#### Go unit tests\n\nStandard Go tests using the `testing` package:\n\n```bash\njust test-go\n```\n\n#### Helm chart unit tests\n\nChart rendering tests implemented with the [helm-unittest](https://github.com/helm-unittest/helm-unittest) plugin:\n\n```bash\njust test-helm\n```\n\n#### End-to-end tests\n\nE2E tests deploy `kube-mgmt` to the local k3d cluster via [devspace](https://devspace.sh) and validate behavior using\n[chainsaw](https://kyverno.github.io/chainsaw/) (Kubernetes-native test framework) and [hurl](https://hurl.dev)\n(HTTP assertions). Each scenario is a directory under `test/e2e/`.\n\nRun a single scenario (shows an interactive picker when no argument is given):\n\n```bash\njust test-e2e [test/e2e/<scenario>]\n```\n\nRun all scenarios sequentially:\n\n```bash\njust test-e2e-all\n```\n\n#### Linting\n\n```bash\njust lint\n```\n\nRuns `go vet` and [staticcheck](https://staticcheck.io) for Go code, and helm-unittest lint rules for the Helm chart.\n\n#### Run all checks\n\n```bash\njust test\n```\n\nRuns lint, Go unit tests, and Helm chart unit tests.\n\n### Release\n\nTo release a new version, create a [GitHub release](https://github.com/open-policy-agent/kube-mgmt/releases)\nwith a tag that follows the [semantic versioning convention](https://semver.org/).\n\nOnce the tag is pushed, the CI pipeline automatically builds and publishes all release artifacts:\nDocker images for all supported architectures and the Helm chart.\n"
  },
  {
    "path": "charts/opa-kube-mgmt/Chart.yaml",
    "content": "apiVersion: v1\nappVersion: 0.0.0 # managed by git tag\nversion: 0.0.0 # managed by git tag\ndescription: Manage OPA in Kubernetes with kube-mgmt sidecar.\nname: opa-kube-mgmt\nkeywords:\n  - opa\n  - admission control\n  - policy\n  - kubernetes\n  - security\nhome: https://www.openpolicyagent.org\nicon: https://raw.githubusercontent.com/open-policy-agent/opa/master/logo/logo.png\nannotations:\n  artifacthub.io/links: |\n    - name: OPA source code\n      url: https://github.com/open-policy-agent/opa\n    - name: kube-mgmt source code\n      url: https://github.com/open-policy-agent/kube-mgmt\n"
  },
  {
    "path": "charts/opa-kube-mgmt/README.md",
    "content": "# Manage OPA in Kubernetes with kube-mgmt sidecar.\n\n[OPA](https://www.openpolicyagent.org) is an open-source general-purpose policy\nengine designed for cloud-native environments.\n\n## Overview\n\nThis helm chart installs `OPA` together with `kube-mgmt` sidecar,\nthat allows to manage OPA policies and data via Kubernetes ``ConfigMaps`.\n\nOptionally, the chart allows to install a [Kubernetes admission\ncontroller](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/).\n\n## Installation\n\n### Prerequisites\n\n- Kubernetes 1.9 (or newer) for validating and mutating webhook admission\n  controller support.\n- Optional, cert-manager (https://docs.cert-manager.io/en/latest/)\n\n### Default Installation\n\nIf you just want to see something run, install the chart with default configuration.\n\n```sh\nhelm repo add opa https://open-policy-agent.github.io/kube-mgmt/charts\nhelm repo update\nhelm upgrade -i -n opa --create-namespace opa opa/opa-kube-mgmt\n```\n\nOnce installed, the OPA will download a sample bundle from https://www.openpolicyagent.org.\nIt contains a simple policy that restricts the hostnames that can be specified on Ingress objects created in the\n`opa-example` namespace.\n\nYou can download the bundle and inspect it yourself:\n\n```sh\nmkdir example && cd example\ncurl -s -L https://www.openpolicyagent.org/bundles/kubernetes/admission | tar xzv\n```\n\n### Installation from GitHub Packages (GHCR)\n\nThe Helm chart and Docker image are also published to GitHub Container Registry (GHCR).\n\nInstall the chart using OCI:\n\n```sh\nhelm upgrade -i -n opa --create-namespace opa \\\n  oci://ghcr.io/open-policy-agent/helm/opa-kube-mgmt --version <version>\n```\n\nThe `kube-mgmt` Docker image is also published to GHCR. To pull it directly:\n\n```sh\n# latest\ndocker pull ghcr.io/open-policy-agent/docker/opa-kube-mgmt:latest\n\n# specific version\ndocker pull ghcr.io/open-policy-agent/docker/opa-kube-mgmt:<version>\n```\n\nTo use the GHCR image when installing the chart:\n\n```sh\nhelm upgrade -i -n opa --create-namespace opa \\\n  oci://ghcr.io/open-policy-agent/helm/opa-kube-mgmt \\\n  --set mgmt.image.repository=ghcr.io/open-policy-agent/docker/opa-kube-mgmt \\\n  --set mgmt.image.tag=latest\n```\n\n## Configuration\n\nAll configuration settings are contained and described in [values.yaml](values.yaml).\n\nYou should set the URL and credentials for the OPA to use to download policies.\nThe URL should identify an HTTP endpoint that implements the [OPA Bundle\nAPI](https://www.openpolicyagent.org/docs/bundles.html).\n\n- `opa.services.controller.url` specifies the base URL of the OPA control plane.\n\n- `opa.services.controller.credentials.bearer.token` specifies a bearer token\n  for the OPA to use to authenticate with the control plane.\n\nFor more information on OPA-specific configuration see the [OPA Configuration\nReference](https://www.openpolicyagent.org/docs/configuration.html).\n\n"
  },
  {
    "path": "charts/opa-kube-mgmt/templates/_helpers.tpl",
    "content": "{{/* vim: set filetype=mustache: */}}\n{{/*\nExpand the name of the chart.\n*/}}\n{{- define \"opa.name\" -}}\n{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix \"-\" -}}\n{{- end -}}\n\n{{/*\nCreate a default fully qualified app name.\nWe truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).\nIf release name contains chart name it will be used as a full name.\n*/}}\n{{- define \"opa.fullname\" -}}\n{{- if .Values.fullnameOverride -}}\n{{- .Values.fullnameOverride | trunc 63 | trimSuffix \"-\" -}}\n{{- else -}}\n{{- $name := default .Chart.Name .Values.nameOverride -}}\n{{- if contains $name .Release.Name -}}\n{{- .Release.Name | trunc 63 | trimSuffix \"-\" -}}\n{{- else -}}\n{{- printf \"%s-%s\" .Release.Name $name | trunc 63 | trimSuffix \"-\" -}}\n{{- end -}}\n{{- end -}}\n{{- end -}}\n\n{{- define \"opa.sarfullname\" -}}\n{{- $name := (include \"opa.fullname\" . | trunc 59 | trimSuffix \"-\") -}}\n{{- printf \"%s-sar\" $name -}}\n{{- end -}}\n\n{{- define \"opa.mgmtfullname\" -}}\n{{- $name := (include \"opa.fullname\" . | trunc 58 | trimSuffix \"-\") -}}\n{{- printf \"%s-mgmt\" $name -}}\n{{- end -}}\n\n{{/*\nCreate chart name and version as used by the chart label.\n*/}}\n{{- define \"opa.chart\" -}}\n{{- printf \"%s-%s\" .Chart.Name .Chart.Version | replace \"+\" \"_\" | trunc 63 | trimSuffix \"-\" -}}\n{{- end -}}\n\n{{/*\nDefine standard labels for frequently used metadata.\n*/}}\n{{- define \"opa.labels.standard\" -}}\napp: {{ template \"opa.fullname\" . }}\nchart: \"{{ .Chart.Name }}-{{ .Chart.Version }}\"\nrelease: \"{{ .Release.Name }}\"\nheritage: \"{{ .Release.Service }}\"\n{{- end -}}\n\n{{/*\nCreate the name of the service account to use\n*/}}\n{{- define \"opa.serviceAccountName\" -}}\n{{- if .Values.serviceAccount.create -}}\n    {{ default (include \"opa.fullname\" .) .Values.serviceAccount.name }}\n{{- else -}}\n    {{ default \"default\" .Values.serviceAccount.name }}\n{{- end -}}\n{{- end -}}\n\n{{- define \"opa.selfSignedIssuer\" -}}\n{{ printf \"%s-selfsign\" (include \"opa.fullname\" .) }}\n{{- end -}}\n\n{{- define \"opa.rootCAIssuer\" -}}\n{{ printf \"%s-ca\" (include \"opa.fullname\" .) }}\n{{- end -}}\n\n{{- define \"opa.rootCACertificate\" -}}\n{{ printf \"%s-ca\" (include \"opa.fullname\" .) }}\n{{- end -}}\n\n{{- define \"opa.servingCertificate\" -}}\n{{ printf \"%s-webhook-tls\" (include \"opa.fullname\" .) }}\n{{- end -}}\n\n{{/*\nDetect the version of cert manager crd that is installed\nError if CRD is not available\n*/}}\n{{- define \"opa.certManagerApiVersion\" -}}\n{{- if (.Capabilities.APIVersions.Has \"cert-manager.io/v1\") -}}\ncert-manager.io/v1\n{{- else if (.Capabilities.APIVersions.Has \"cert-manager.io/v1beta1\") -}}\ncert-manager.io/v1beta1\n{{- else  -}}\n{{- fail \"cert-manager CRD does not appear to be installed\" }}\n{{- end -}}\n{{- end -}}\n\n{{/*\nDetect the available version of admissionregistration\n*/}}\n{{- define \"opa.admissionregistrationApiVersion\" -}}\n{{- if (.Capabilities.APIVersions.Has \"admissionregistration.k8s.io/v1\") -}}\nadmissionregistration.k8s.io/v1\n{{- else  -}}\nadmissionregistration.k8s.io/v1beta1\n{{- end -}}\n{{- end -}}\n\n{{- define \"opa.mgmt.image\" -}}\n{{- $tag := .Values.mgmt.image.tag | default .Chart.AppVersion -}}\n{{ printf \"%s:%s\" .Values.mgmt.image.repository $tag }}\n{{- end -}}\n\n{{- define \"opa.dnsPolicy\" -}}\n{{- if .Values.dnsPolicyOverride -}}\ndnsPolicy: \"{{ .Values.dnsPolicyOverride }}\"\n{{ else if .Values.hostNetwork.enabled -}}\ndnsPolicy: \"ClusterFirstWithHostNet\"\n{{ end -}}\n{{ end -}}\n"
  },
  {
    "path": "charts/opa-kube-mgmt/templates/deployment.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ template \"opa.fullname\" . }}\n  labels:\n{{ include \"opa.labels.standard\" . | indent 4 }}\nspec:\n  replicas: {{ .Values.replicas }}\n  selector:\n    matchLabels:\n      app: {{ template \"opa.fullname\" . }}\n  {{- with .Values.deploymentStrategy }}\n  strategy:\n  {{- toYaml . | nindent 4 }}\n  {{- end }}\n  template:\n    metadata:\n      annotations:\n        {{- if .Values.opa }}\n        checksum/config: {{ tpl (toYaml .Values.opa) . | sha256sum }}\n        {{- end }}\n        {{- if .Values.admissionController.enabled }}\n        checksum/webhookconfiguration: {{ include (print $.Template.BasePath \"/webhookconfiguration.yaml\" ) . | sha256sum }}\n        {{- end }}\n{{- if .Values.annotations }}\n{{ toYaml .Values.annotations | indent 8 }}\n{{- end }}\n      labels:\n        app: {{ template \"opa.fullname\" . }}\n      name: {{ template \"opa.fullname\" . }}\n    spec:\n{{- if .Values.imagePullSecrets }}\n      imagePullSecrets:\n      {{- range .Values.imagePullSecrets }}\n        - name: {{ . }}\n      {{- end }}\n{{- end }}\n{{- if .Values.priorityClassName }}\n      priorityClassName: {{ .Values.priorityClassName }}\n{{- end }}\n{{- if or .Values.authz.enabled .Values.bootstrapPolicies}}\n      initContainers:\n        - name: initpolicy\n          image: {{ include \"opa.mgmt.image\" . }}\n          imagePullPolicy: {{ .Values.mgmt.image.pullPolicy }}\n          resources:\n{{ toYaml .Values.mgmt.resources | indent 12 }}\n          command:\n          - /bin/sh\n          - -c\n          - |\n{{- if .Values.authz.enabled }}\n{{- if .Values.authz.mgmtToken}}\n            cat /mgmt-token-secret/mgmt-token > /bootstrap/mgmt-token\n{{- else }}\n            tr -dc 'A-F0-9' < /dev/urandom | dd bs=1 count=32 2>/dev/null > /bootstrap/mgmt-token\n{{- end }}\n            TOKEN=`cat /bootstrap/mgmt-token`\n            cat > /bootstrap/authz.rego <<EOF\n            package system.authz\n            import rego.v1\n            default allow := false\n            # Allow anonymous access to the default policy decision.\n            allow if { input.path = [\"\"]; input.method == \"POST\" }\n            allow if { input.path = [\"\"]; input.method == \"GET\" }\n            # This is only used for health check in liveness and readiness probe\n            allow if { input.path = [\"health\"]; input.method == \"GET\" }\n{{- if .Values.prometheus.enabled }}\n            # This allows metrics to be scraped by prometheus\n            allow if { input.path = [\"metrics\"]; input.method == \"GET\" }\n{{- end }}\n            allow if { input.identity == \"$TOKEN\" }\n            EOF\n{{- end }}\n{{- range $policyName, $policy := .Values.bootstrapPolicies }}\n            cat > /bootstrap/{{ $policyName }}.rego <<EOF\n{{ $policy | indent 12 }}\n            EOF\n{{- end }}\n          volumeMounts:\n            - name: bootstrap\n              mountPath: /bootstrap\n{{- if .Values.authz.mgmtToken}}\n            - name: mgmt-token-secret\n              mountPath: /mgmt-token-secret\n              readOnly: true\n{{- end }}\n{{- end }}\n{{- if .Values.hostNetwork.enabled }}\n      hostNetwork: true\n{{- end }}\n      {{- include \"opa.dnsPolicy\" . | nindent 6 -}}\n      containers:\n        - name: opa\n          ports:\n          - name: opa\n            containerPort: {{ .Values.port }}\n{{- if .Values.prometheus.enabled }}\n          - name: diag\n            containerPort: {{ .Values.prometheus.port }}\n{{- end }}\n          image: \"{{ .Values.image.repository }}:{{ .Values.image.tag }}\"\n          imagePullPolicy: {{ .Values.image.pullPolicy }}\n          env:\n{{- if .Values.extraEnv }}\n{{ toYaml .Values.extraEnv | indent 12 }}\n{{- end }}\n          resources:\n{{ toYaml .Values.resources | indent 12 }}\n          args:\n            - \"run\"\n            - \"--server\"\n            {{- if .Values.opa }}\n            - \"--config-file=/config/config.yaml\"\n            {{- end }}\n            {{- if .Values.useHttps }}\n            - \"--tls-cert-file=/certs/tls.crt\"\n            - \"--tls-private-key-file=/certs/tls.key\"\n            {{- end }}\n            - \"--addr=0.0.0.0:{{ .Values.port }}\"\n            - \"--log-level={{ .Values.logLevel }}\"\n            - \"--log-format={{ .Values.logFormat }}\"\n            {{- if .Values.authz.enabled }}\n            - \"--authentication=token\"\n            - \"--authorization=basic\"\n            - \"--ignore=.*\"\n            {{- end }}\n            {{- if .Values.prometheus.enabled }}\n            - \"--diagnostic-addr=http://0.0.0.0:{{ .Values.prometheus.port }}\"\n            {{- end }}\n            {{- if or .Values.authz.enabled .Values.bootstrapPolicies }}\n            - \"/bootstrap\"\n            {{- end }}\n            {{- range .Values.extraArgs }}\n            - {{ . }}\n            {{- end }}\n          volumeMounts:\n            {{- if .Values.useHttps }}\n            - name: certs\n              readOnly: true\n              mountPath: /certs\n            {{- end }}\n            {{- if .Values.opa }}\n            - name: config\n              readOnly: true\n              mountPath: /config\n            {{- end }}\n{{- if or .Values.authz.enabled .Values.bootstrapPolicies }}\n            - name: bootstrap\n              readOnly: true\n              mountPath: /bootstrap\n{{- end }}\n{{- if .Values.extraVolumeMounts }}\n{{ toYaml .Values.extraVolumeMounts | indent 12}}\n{{- end }}\n          readinessProbe:\n            httpGet:\n              path: /health\n              scheme: {{ .Values.useHttps | ternary \"HTTPS\" \"HTTP\" }}\n              port: opa\n            initialDelaySeconds: 5\n            periodSeconds: 10\n          livenessProbe:\n            httpGet:\n              path: /health\n              scheme: {{ .Values.useHttps | ternary \"HTTPS\" \"HTTP\" }}\n              port: opa\n            initialDelaySeconds: 10\n            periodSeconds: 15\n{{- if .Values.mgmt.enabled }}\n        - name: mgmt\n          image: {{ include \"opa.mgmt.image\" . }}\n          imagePullPolicy: {{ .Values.mgmt.image.pullPolicy }}\n          startupProbe:\n{{ toYaml .Values.mgmt.startupProbe | nindent 12 }}\n          env:\n{{- if .Values.mgmt.extraEnv }}\n{{ toYaml .Values.mgmt.extraEnv | indent 12 }}\n{{- end }}\n          resources:\n            {{ toYaml .Values.mgmt.resources | nindent 12 }}\n          args:\n            {{- if .Values.authz.enabled }}\n            - --opa-auth-token-file=/bootstrap/mgmt-token\n            {{- end }}\n            - --opa-url={{ .Values.useHttps | ternary \"https\" \"http\" }}://127.0.0.1:{{ .Values.port }}/v1\n            - --opa-allow-insecure\n            - \"--namespaces={{ coalesce .Values.mgmt.namespaces (list .Release.Namespace) | join \",\" }}\"\n            - \"--enable-data={{ .Values.mgmt.data.enabled }}\"\n            - \"--enable-policies={{ .Values.mgmt.policies.enabled }}\"\n\n            - \"--replicate-path={{ .Values.mgmt.replicate.path }}\"\n            {{- range .Values.mgmt.replicate.namespace }}\n            - \"--replicate={{ . }}\"\n            {{- end }}\n            {{- range .Values.mgmt.replicate.cluster }}\n            - \"--replicate-cluster={{ . }}\"\n            {{- end }}\n            {{- if .Values.mgmt.replicate.ignoreNs }}\n            - \"--replicate-ignore-namespaces={{ .Values.mgmt.replicate.ignoreNs | join \",\" }}\"\n            {{- else }}\n            - \"--replicate-ignore-namespaces=\"\n            {{- end }}\n            {{- if .Values.mgmt.replicate.auto }}\n            - \"--opa-config=/config/config.yaml\"\n            - \"--health-endpoint=0.0.0.0:8000\"\n            {{- end }}\n            {{- range .Values.mgmt.extraArgs }}\n            - {{ . }}\n            {{- end }}\n          volumeMounts:\n{{- if or .Values.authz.enabled .Values.bootstrapPolicies }}\n            - name: bootstrap\n              readOnly: true\n              mountPath: /bootstrap\n{{- end }}\n{{- if .Values.opa }}\n            - name: config\n              readOnly: true\n              mountPath: /config\n{{- end }}\n{{- if .Values.extraVolumeMounts }}\n{{ toYaml .Values.extraVolumeMounts | indent 12}}\n{{- end }}\n{{- if .Values.mgmt.replicate.auto }}\n          readinessProbe:\n            httpGet:\n              path: /health\n              port: 8000\n            initialDelaySeconds: 5\n            periodSeconds: 10\n          livenessProbe:\n            httpGet:\n              path: /health\n              port: 8000\n            initialDelaySeconds: 10\n            periodSeconds: 15\n{{- end }}\n{{- end }}\n{{- if .Values.sar.enabled }}\n        - name: sarproxy\n          image: {{ .Values.sar.image.repository }}:{{ .Values.sar.image.tag }}\n          imagePullPolicy: {{ .Values.sar.image.pullPolicy }}\n          resources:\n{{ toYaml .Values.sar.resources | indent 12 }}\n          command:\n            - kubectl\n            - proxy\n            - --accept-paths=^/apis/authorization.k8s.io/v1/subjectaccessreviews$\n{{- end }}\n{{- if .Values.extraContainers }}\n{{ toYaml .Values.extraContainers | indent 8}}\n{{- end }}\n      {{- if .Values.securityContext.enabled }}\n      securityContext:\n        {{- range $key, $val := .Values.securityContext }}\n        {{- if ne $key \"enabled\" }}\n        {{ $key }}: {{ toYaml $val | nindent 10 }}\n        {{- end }}\n        {{- end }}\n      {{- end }}\n      serviceAccountName: {{ template \"opa.serviceAccountName\" .}}\n      volumes:\n        {{- if .Values.useHttps }}\n        - name: certs\n          secret:\n            secretName: {{ template \"opa.fullname\" . }}-cert\n        {{- end }}\n        {{- if .Values.opa }}\n        - name: config\n          secret:\n            secretName: {{ template \"opa.fullname\" . }}-config\n        {{- end }}\n{{- if or .Values.authz.enabled .Values.bootstrapPolicies}}\n        - name: bootstrap\n          emptyDir: {}\n{{- if .Values.authz.mgmtToken}}\n        - name: mgmt-token-secret\n          secret:\n            secretName: {{.Values.authz.mgmtToken.secretName}}\n            items:\n              - key: {{ .Values.authz.mgmtToken.secretKey | default \"mgmtToken\" }}\n                path: mgmt-token\n{{- end }}\n{{- end }}\n{{- if .Values.extraVolumes }}\n{{ toYaml .Values.extraVolumes | indent 8}}\n{{- end }}\n      affinity:\n{{ toYaml .Values.affinity | indent 8 }}\n      nodeSelector:\n{{ toYaml .Values.nodeSelector | indent 8 }}\n      tolerations:\n{{ toYaml .Values.tolerations | indent 8 }}\n      topologySpreadConstraints:\n{{ toYaml .Values.topologySpreadConstraints | indent 8 }}\n"
  },
  {
    "path": "charts/opa-kube-mgmt/templates/ingressroute.yaml",
    "content": "{{- if .Values.e2e }}\napiVersion: traefik.io/v1alpha1\nkind: IngressRouteTCP\nmetadata:\n  name: {{ include \"opa.fullname\" . }}-websecure\nspec:\n  entryPoints:\n    - websecure\n  routes:\n    - match: HostSNI(`*`)\n      services:\n        - name: {{ include \"opa.fullname\" . }}\n          namespace: {{ .Release.Namespace }}\n          port: {{ .Values.port }}\n  tls:\n    passthrough: true\n---\napiVersion: traefik.io/v1alpha1\nkind: IngressRouteTCP\nmetadata:\n  name: {{ include \"opa.fullname\" . }}-web\nspec:\n  entryPoints:\n    - web\n  routes:\n    - match: HostSNI(`*`)\n      services:\n        - name: {{ include \"opa.fullname\" . }}\n          namespace: {{ .Release.Namespace }}\n          port: {{ .Values.port }}\n{{- end }}\n"
  },
  {
    "path": "charts/opa-kube-mgmt/templates/mgmt-token-secret.yaml",
    "content": "{{- if .Values.e2eMgmtTokenSecret -}}\napiVersion: v1\nkind: Secret\nmetadata:\n  name: mgmt-token-secret\ntype: Opaque\nstringData:\n  mgmtToken: mgmt-token-secret-value\n{{- end -}}"
  },
  {
    "path": "charts/opa-kube-mgmt/templates/poddisruptionbudget.yaml",
    "content": "{{- if .Values.podDisruptionBudget.enabled }}\n{{- if .Capabilities.APIVersions.Has \"policy/v1/PodDisruptionBudget\" }}\napiVersion: policy/v1\n{{- else }}\napiVersion: policy/v1beta1\n{{- end }}\nkind: PodDisruptionBudget\nmetadata:\n  name: {{ template \"opa.fullname\" . }}\n  labels:\n{{ include \"opa.labels.standard\" . | indent 4 }}\nspec:\n{{- if .Values.podDisruptionBudget.minAvailable }}\n  minAvailable: {{ .Values.podDisruptionBudget.minAvailable }}\n{{- end }}\n{{- if .Values.podDisruptionBudget.maxUnavailable }}\n  maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }}\n{{- end }}\n  selector:\n    matchLabels:\n      app: {{ template \"opa.fullname\" . }}\n{{- end }}\n"
  },
  {
    "path": "charts/opa-kube-mgmt/templates/rbac-mgmt-replicate.yaml",
    "content": "{{- if and .Values.rbac.create .Values.mgmt.enabled -}}\n{{- $arr := concat .Values.mgmt.replicate.cluster .Values.mgmt.replicate.namespace -}}\n{{- if or (gt (len $arr) 0) .Values.mgmt.replicate.auto }}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app: {{ template \"opa.name\" . }}\n    chart: {{ template \"opa.chart\" . }}\n    release: {{ .Release.Name }}\n    component: mgmt\n  name: \"{{ template \"opa.mgmtfullname\" . }}-repl\"\nrules:\n  {{- with .Values.rbac.extraRules }}\n  {{ . | toYaml | nindent 2 }}\n  {{- end }}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    app: {{ template \"opa.name\" . }}\n    chart: {{ template \"opa.chart\" . }}\n    release: {{ .Release.Name }}\n    component: mgmt\n  name: \"{{ template \"opa.mgmtfullname\" . }}-repl\"\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: \"{{ template \"opa.mgmtfullname\" . }}-repl\"\nsubjects:\n  - kind: ServiceAccount\n    name: {{ template \"opa.serviceAccountName\" . }}\n    namespace: {{ .Release.Namespace }}\n{{- end }}\n{{- end -}}\n"
  },
  {
    "path": "charts/opa-kube-mgmt/templates/rbac-mgmt.yaml",
    "content": "{{- define \"opa.rbac.cm.rules\" -}}\nrules:\n  - apiGroups: [\"\"]\n    resources: [\"configmaps\"]\n    verbs: [\"get\", \"list\", \"watch\", \"update\", \"patch\"]\n{{- end -}}\n\n{{- if and .Values.rbac.create .Values.mgmt.enabled -}}\n{{- if eq (.Values.mgmt.namespaces | join \",\") \"*\" }}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app: {{ template \"opa.name\" . }}\n    chart: {{ template \"opa.chart\" . }}\n    release: {{ .Release.Name }}\n    component: mgmt\n  name: {{ template \"opa.mgmtfullname\" . }}\n{{ include \"opa.rbac.cm.rules\" . }}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    app: {{ template \"opa.name\" . }}\n    chart: {{ template \"opa.chart\" . }}\n    release: {{ .Release.Name }}\n    component: mgmt\n  name: {{ template \"opa.mgmtfullname\" . }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: {{ template \"opa.mgmtfullname\" . }}\nsubjects:\n  - kind: ServiceAccount\n    name: {{ template \"opa.serviceAccountName\" . }}\n    namespace: {{ .Release.Namespace }}\n{{- else if and (eq (kindOf .Values.mgmt.namespaces) \"slice\") (gt (len .Values.mgmt.namespaces) 0) }}\n{{- range .Values.mgmt.namespaces }}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app: {{ template \"opa.name\" $ }}\n    chart: {{ template \"opa.chart\" $ }}\n    release: {{ $.Release.Name }}\n    component: mgmt\n  name: {{ template \"opa.mgmtfullname\" $ }}\n  namespace: {{ . }}\n{{ include \"opa.rbac.cm.rules\" $ }}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app: {{ template \"opa.name\" $ }}\n    chart: {{ template \"opa.chart\" $ }}\n    release: {{ $.Release.Name }}\n    component: mgmt\n  name: {{ template \"opa.mgmtfullname\" $ }}\n  namespace: {{ . }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: {{ template \"opa.mgmtfullname\" $ }}\nsubjects:\n  - kind: ServiceAccount\n    name: {{ template \"opa.serviceAccountName\" $ }}\n    namespace: {{ $.Release.Namespace }}\n{{- end }}\n{{- else }}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  labels:\n    app: {{ template \"opa.name\" . }}\n    chart: {{ template \"opa.chart\" . }}\n    release: {{ .Release.Name }}\n    component: mgmt\n  name: {{ template \"opa.mgmtfullname\" . }}\n  namespace: {{ .Release.Namespace }}\n{{ include \"opa.rbac.cm.rules\" . }}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  labels:\n    app: {{ template \"opa.name\" . }}\n    chart: {{ template \"opa.chart\" . }}\n    release: {{ .Release.Name }}\n    component: mgmt\n  name: {{ template \"opa.mgmtfullname\" . }}\n  namespace: {{ .Release.Namespace }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: {{ template \"opa.mgmtfullname\" . }}\nsubjects:\n  - kind: ServiceAccount\n    name: {{ template \"opa.serviceAccountName\" . }}\n    namespace: {{ .Release.Namespace }}\n{{- end }}\n{{- end -}}\n"
  },
  {
    "path": "charts/opa-kube-mgmt/templates/rbac-sar.yaml",
    "content": "{{- if (and .Values.rbac.create .Values.sar.enabled) -}}\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  labels:\n    app: {{ template \"opa.name\" . }}\n    chart: {{ template \"opa.chart\" . }}\n    heritage: {{ .Release.Service }}\n    release: {{ .Release.Name }}\n    component: sar\n  name: {{ template \"opa.sarfullname\" . }}\nrules:\n  - apiGroups:\n      - \"authorization.k8s.io\"\n    resources:\n    - subjectaccessreviews\n    verbs:\n    - create\n---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  labels:\n    app: {{ template \"opa.name\" . }}\n    chart: {{ template \"opa.chart\" . }}\n    heritage: {{ .Release.Service }}\n    release: {{ .Release.Name }}\n    component: sar\n  name: {{ template \"opa.sarfullname\" . }}\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: {{ template \"opa.sarfullname\" . }}\nsubjects:\n  - kind: ServiceAccount\n    name: {{ template \"opa.serviceAccountName\" . }}\n    namespace: {{ .Release.Namespace }}\n{{- end -}}\n"
  },
  {
    "path": "charts/opa-kube-mgmt/templates/secret-opa-config.yaml",
    "content": "{{- if .Values.opa -}}\napiVersion: v1\nkind: Secret\nmetadata:\n  name: {{ template \"opa.fullname\" . }}-config\n  labels:\n{{ include \"opa.labels.standard\" . | indent 4 }}\ntype: Opaque\ndata:\n  config.yaml: {{ toYaml .Values.opa | b64enc }}\n{{- end -}}"
  },
  {
    "path": "charts/opa-kube-mgmt/templates/service.yaml",
    "content": "kind: Service\napiVersion: v1\nmetadata:\n  name: {{ template \"opa.fullname\" . }}\n  labels:\n{{ include \"opa.labels.standard\" . | indent 4 }}\n{{- with .Values.service.annotations }}\n  annotations:\n    {{- toYaml . | nindent 4 }}\n{{- end }}\nspec:\n  selector:\n    app: {{ template \"opa.fullname\" . }}\n  ports:\n  - name: opa\n    port: {{ .Values.port }}\n    targetPort: opa\n{{- if .Values.prometheus.enabled }}\n  - name: diag\n    port: {{ .Values.prometheus.port }}\n    targetPort: diag\n{{- end }}\n{{- if .Values.extraPorts }}\n{{ toYaml .Values.extraPorts | indent 2}}\n{{- end }}\n{{- if .Values.service.trafficDistribution }}\n  trafficDistribution: {{ .Values.service.trafficDistribution }}\n{{- end }}\n"
  },
  {
    "path": "charts/opa-kube-mgmt/templates/serviceaccount.yaml",
    "content": "{{- if .Values.serviceAccount.create }}\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: {{ template \"opa.serviceAccountName\" .}}\n  {{- with .Values.serviceAccount.annotations }}\n  annotations:\n    {{ toYaml . }}\n  {{- end }}\n  labels:\n    app: {{ template \"opa.fullname\" . }}\n    chart: {{ template \"opa.chart\" . }}\n    release: \"{{ .Release.Name }}\"\n    heritage: \"{{ .Release.Service }}\"\n{{- end }}\n"
  },
  {
    "path": "charts/opa-kube-mgmt/templates/servicemonitor.yaml",
    "content": "{{- if and (.Capabilities.APIVersions.Has \"monitoring.coreos.com/v1\") .Values.prometheus.enabled .Values.serviceMonitor.enabled }}\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  labels:\n    app: {{ template \"opa.name\" . }}\n    chart: {{ template \"opa.chart\" . }}\n    heritage: {{ .Release.Service }}\n    {{- if not .Values.serviceMonitor.additionalLabels.release }}\n    release: {{ .Release.Name }}\n    {{- end }}\n    {{- if .Values.serviceMonitor.additionalLabels }}\n    {{- toYaml .Values.serviceMonitor.additionalLabels | nindent 4}}\n    {{- end }}\n  name: {{ template \"opa.fullname\" . }}\n  {{- if .Values.serviceMonitor.namespace }}\n  namespace: {{ .Values.serviceMonitor.namespace }}\n  {{- end }}\nspec:\n  endpoints:\n  - port: diag\n    interval: {{ .Values.serviceMonitor.interval }}\n  jobLabel: {{ template \"opa.fullname\" . }}\n  namespaceSelector:\n    matchNames:\n    - {{ .Release.Namespace }}\n  selector:\n    matchLabels:\n      app: {{ template \"opa.fullname\" . }}\n      release: {{ .Release.Name }}\n{{- end }}\n"
  },
  {
    "path": "charts/opa-kube-mgmt/templates/webhookconfiguration.yaml",
    "content": "{{- $cn := printf \"%s.%s.svc\" ( include \"opa.fullname\" . ) .Release.Namespace }}\n{{- $ca := genCA \"opa-admission-ca\" 3650 -}}\n{{- $cert := genSignedCert $cn nil (list $cn) 3650 $ca -}}\n{{- if .Values.admissionController.enabled }}\nkind: {{ .Values.admissionController.kind }}\napiVersion: {{ include \"opa.admissionregistrationApiVersion\" . }}\nmetadata:\n  name: {{ template \"opa.fullname\" . }}\n  annotations:\n{{- if .Values.certManager.enabled }}\n    certmanager.k8s.io/inject-ca-from: {{ printf \"%s/%s\" .Release.Namespace (include \"opa.rootCACertificate\" .) | quote }}\n    cert-manager.io/inject-ca-from: {{ printf \"%s/%s\" .Release.Namespace (include \"opa.rootCACertificate\" .) | quote }}\n{{- end }}\n{{- if .Values.admissionController.annotations }}\n{{ toYaml .Values.admissionController.annotations | indent 4 }}\n{{- end }}\n  labels:\n{{ include \"opa.labels.standard\" . | indent 4 }}\nwebhooks:\n  - name: webhook.openpolicyagent.org\n    admissionReviewVersions: [\"v1\", \"v1beta1\"]\n{{- with .Values.admissionController.namespaceSelector }}\n    namespaceSelector:\n{{ toYaml . | indent 6 }}\n{{ end }}\n    failurePolicy: {{ .Values.admissionController.failurePolicy }}\n    rules:\n{{ toYaml .Values.admissionController.rules | indent 6 }}\n    clientConfig:\n      {{- if .Values.useHttps }}\n      {{- if not .Values.certManager.enabled }}\n      {{- if .Values.generateCerts }}\n      caBundle: {{ b64enc $ca.Cert }}\n      {{- else }}\n      caBundle: {{ b64enc .Values.CA }}\n      {{- end }}\n      {{- end }}\n      {{- end }}\n      service:\n        name: {{ template \"opa.fullname\" . }}\n        namespace: {{ .Release.Namespace }}\n        port: {{ .Values.port }}\n    sideEffects: {{ .Values.admissionController.sideEffect }}\n    {{- if .Values.timeoutSeconds }}\n    timeoutSeconds: {{ .Values.timeoutSeconds }}\n    {{- end }}\n{{- end }}\n\n{{/* HTTP certificates  */}}\n\n{{- if .Values.useHttps }}\n{{- if .Values.certManager.enabled }}\n--- {{/* Create a selfsigned Issuer, in order to create a root CA certificate for signing webhook serving certificates */}}\napiVersion: {{ include \"opa.certManagerApiVersion\" . }}\nkind: Issuer\nmetadata:\n{{- if .Values.admissionController.annotations }}\n  annotations:\n{{ toYaml .Values.admissionController.annotations | indent 4 }}\n{{- end }}\n  name: {{ include \"opa.selfSignedIssuer\" . }}\n  labels:\n{{ include \"opa.labels.standard\" . | indent 4 }}\nspec:\n  selfSigned: {}\n--- {{/* Generate a CA Certificate used to sign certificates for the webhook */}}\napiVersion: {{ include \"opa.certManagerApiVersion\" . }}\nkind: Certificate\nmetadata:\n{{- if .Values.admissionController.annotations }}\n  annotations:\n{{ toYaml .Values.admissionController.annotations | indent 4 }}\n{{- end }}\n  name: {{ include \"opa.rootCACertificate\" . }}\n  labels:\n{{ include \"opa.labels.standard\" . | indent 4 }}\nspec:\n  secretName: {{ include \"opa.rootCACertificate\" . }}\n  duration: {{ .Values.certManager.rootCACertificateDuration | quote }}\n  issuerRef:\n    name: {{ include \"opa.selfSignedIssuer\" . }}\n  commonName: \"ca.webhook.opa\"\n  isCA: true\n--- {{/* Create an Issuer that uses the above generated CA certificate to issue certs */}}\napiVersion: {{ include \"opa.certManagerApiVersion\" . }}\nkind: Issuer\nmetadata:\n{{- if .Values.admissionController.annotations }}\n  annotations:\n{{ toYaml .Values.admissionController.annotations | indent 4 }}\n{{- end }}\n  name: {{ include \"opa.rootCAIssuer\" . }}\n  labels:\n{{ include \"opa.labels.standard\" . | indent 4 }}\nspec:\n  ca:\n    secretName: {{ include \"opa.rootCACertificate\" . }}\n--- {{/* Finally, generate a serving certificate for the webhook to use */}}\napiVersion: {{ include \"opa.certManagerApiVersion\" . }}\nkind: Certificate\nmetadata:\n{{- if .Values.admissionController.annotations }}\n  annotations:\n{{ toYaml .Values.admissionController.annotations | indent 4 }}\n{{- end }}\n  name: {{ include \"opa.servingCertificate\" . }}\n  labels:\n{{ include \"opa.labels.standard\" . | indent 4 }}\nspec:\n  secretName: {{ template \"opa.fullname\" . }}-cert\n  duration: {{ .Values.certManager.servingCertificateDuration | quote }}\n  issuerRef:\n    name: {{ include \"opa.rootCAIssuer\" . }}\n  dnsNames:\n  - {{ include \"opa.fullname\" . }}\n  - {{ include \"opa.fullname\" . }}.{{ .Release.Namespace }}\n  - {{ include \"opa.fullname\" . }}.{{ .Release.Namespace }}.svc\n{{- else }}\n---  {{/* create static secret  */}}\napiVersion: v1\nkind: Secret\nmetadata:\n{{- if .Values.admissionController.annotations }}\n  annotations:\n{{ toYaml .Values.admissionController.annotations | indent 4 }}\n{{- end }}\n  name: {{ template \"opa.fullname\" . }}-cert\n  labels:\n    app: {{ template \"opa.fullname\" . }}\n    chart: \"{{ .Chart.Name }}-{{ .Chart.Version }}\"\n    release: \"{{ .Release.Name }}\"\n    heritage: \"{{ .Release.Service }}\"\ntype: Opaque\ndata:\n{{- if .Values.generateCerts }}\n  tls.crt: {{ b64enc $cert.Cert }}\n  tls.key: {{ b64enc $cert.Key }}\n{{- else }}\n  tls.crt: {{ b64enc .Values.cert }}\n  tls.key: {{ b64enc .Values.key }}\n{{- end }}\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "charts/opa-kube-mgmt/values.schema.json",
    "content": "{\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n  \"$id\": \"https://github.com/open-policy-agent/kube-mgmt\",\n  \"title\": \"kube-mgmt helm values\",\n\n  \"definitions\": {\n    \"image\": {\n      \"type\": \"object\", \"title\": \"OPA docker image configuration\", \"required\": [\"repository\", \"tag\"],\n      \"properties\": {\n        \"repository\": {\"type\": \"string\"},\n        \"tag\": {\"type\": \"string\"},\n        \"pullPolicy\": {\"type\": \"string\", \"default\": \"IfNotPresent\"}\n      }\n    }\n  },\n\n  \"type\": \"object\", \"required\": [\"image\", \"mgmt\"], \"additionalProperties\": true,\n  \"properties\": {\n    \"image\": {\"$ref\":  \"#/definitions/image\"},\n    \"mgmt\": {\n      \"type\": \"object\", \"additionalProperties\": true, \"required\": [\"image\", \"enabled\"],\n      \"properties\": {\n        \"enabled\": {\"type\": \"boolean\", \"default\": true},\n        \"image\": {\"$ref\": \"#/definitions/image\"}\n      }\n    },\n    \"serviceAccount\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"create\": {\"type\": \"boolean\", \"default\": true},\n        \"annotations\": {\"type\": \"object\", \"additionalProperties\": {\"type\": \"string\"}, \"default\": {}},\n        \"name\": {\"type\": [\"string\", \"null\"], \"default\": null}\n      }\n    },\n    \"service\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"annotations\": {\n          \"type\": \"object\",\n          \"additionalProperties\": {\"type\": \"string\"},\n          \"default\": {}\n        },\n        \"trafficDistribution\": {\n          \"type\": [\"null\",\"string\"],\n          \"enum\": [\"PreferClose\", \"PreferSameNode\", \"PreferSameZone\", null],\n          \"default\": null\n        }\n      }\n    },\n    \"topologySpreadConstraints\": {\n      \"type\": \"array\",\n      \"items\": {\n        \"type\": \"object\",\n        \"required\": [\"maxSkew\", \"topologyKey\", \"whenUnsatisfiable\"],\n        \"properties\": {\n          \"maxSkew\": {\n            \"type\": \"integer\",\n            \"minimum\": 1\n          },\n          \"topologyKey\": {\n            \"type\": \"string\",\n            \"minLength\": 1\n          },\n          \"whenUnsatisfiable\": {\n            \"type\": \"string\",\n            \"enum\": [\"DoNotSchedule\", \"ScheduleAnyway\"]\n          },\n          \"labelSelector\": {\n            \"type\": \"object\",\n            \"properties\": {\n              \"matchLabels\": {\n                \"type\": \"object\",\n                \"additionalProperties\": {\"type\": \"string\"}\n              },\n              \"matchExpressions\": {\n                \"type\": \"array\",\n                \"items\": {\n                  \"type\": \"object\",\n                  \"required\": [\"key\", \"operator\"],\n                  \"properties\": {\n                    \"key\": {\"type\": \"string\"},\n                    \"operator\": {\"type\": \"string\", \"enum\": [\"In\", \"NotIn\", \"Exists\", \"DoesNotExist\"]},\n                    \"values\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}}\n                  }\n                }\n              }\n            }\n          },\n          \"matchLabelKeys\": {\n            \"type\": \"array\",\n            \"items\": {\"type\": \"string\"}\n          },\n          \"minDomains\": {\n            \"type\": \"integer\",\n            \"minimum\": 1\n          },\n          \"nodeAffinityPolicy\": {\n            \"type\": \"string\",\n            \"enum\": [\"Honor\", \"Ignore\"]\n          },\n          \"nodeTaintsPolicy\": {\n            \"type\": \"string\",\n            \"enum\": [\"Honor\", \"Ignore\"]\n          }\n        }\n      },\n      \"default\": []\n    }\n  }\n}\n"
  },
  {
    "path": "charts/opa-kube-mgmt/values.yaml",
    "content": "# Default values for opa.\n# -----------------------\n#\n# OPA configuration file. See https://www.openpolicyagent.org/docs/configuration.html for more details.\nopa: {}\n\n# Setup the webhook using cert-manager\ncertManager:\n  enabled: false\n  rootCACertificateDuration: 43800h # 5y\n  servingCertificateDuration: 8760h # 1y\n\n# Expose the prometheus scraping endpoint\nprometheus:\n  enabled: false\n  port: 8182\n\n## ServiceMonitor consumed by prometheus-operator\nserviceMonitor:\n  ## If the operator is installed in your cluster, set to true to create a Service Monitor Entry\n  enabled: false\n  interval: \"15s\"\n  ## Namespace in which the service monitor is created\n  # namespace: monitoring\n  # Added to the ServiceMonitor object so that prometheus-operator is able to discover it\n  ## ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#prometheusspec\n  additionalLabels: {}\n\n# Service configuration\nservice:\n  # Annotations to add to the service\n  annotations: {}\n  # Configure trafficDistribution if needed.\n  # trafficDistribution: PreferSameZone\n\n# Annotations in the deployment template\nannotations: {}\n\n# Bootstrap policies to load upon startup\n# Define policies in the form of:\n# <policyName> : |-\n#   <regoBody>\n# For example, to mask the entire input body in the decision logs:\n# bootstrapPolicies:\n#   log: |-\n#     package system.log\n#     mask[\"/input\"]\nbootstrapPolicies: {}\n\n# Admission controller configuration.\nadmissionController:\n  enabled: false\n\n  # To enforce mutating policies, change to MutatingWebhookConfiguration.\n  kind: ValidatingWebhookConfiguration\n\n  # To set annotations on all admissionController resources (Secret/Certificate/Issuer/AdmissionController)\n  # annotations:\n  #   example: value\n\n  # To _fail closed_ on failures, change to Fail. During initial testing, we\n  # recommend leaving the failure policy as Ignore.\n  failurePolicy: Ignore\n\n  # Adds a namespace selector to the admission controller webhook\n  namespaceSelector:\n    matchExpressions:\n      - {key: openpolicyagent.org/webhook, operator: NotIn, values: [ignore]}\n\n  # SideEffectClass for the webhook, setting to NoneOnDryRun enables dry-run.\n  # Only None and NoneOnDryRun are permitted for admissionregistration.k8s.io/v1.\n  sideEffect: None\n\n  # To restrict the kinds of operations and resources that are subject to OPA\n  # policy checks, see the settings below. By default, all resources and\n  # operations are subject to OPA policy checks.\n  rules:\n    - operations: [\"*\"]\n      apiGroups: [\"*\"]\n      apiVersions: [\"*\"]\n      resources: [\"*\"]\n\n# The helm Chart will automatically generate a CA and server certificate for\n# the OPA. If you want to supply your own certificates, set the field below to\n# false and add the PEM encoded CA certificate and server key pair below.\n#\n# WARNING: The common name name in the server certificate MUST match the\n# hostname of the service that exposes the OPA to the apiserver. For example.\n# if the service name is created in the \"default\" nanamespace with name \"opa\"\n# the common name MUST be set to \"opa.default.svc\".\n#\n# If the common name is not set correctly, the apiserver will refuse to\n# communicate with the OPA.\ngenerateCerts: true\nCA: \"\"\ncert: \"\"\nkey: \"\"\n\n# Controls a PodDisruptionBudget for the OPA pod. Suggested use if having opa\n# always running for admission control is important\npodDisruptionBudget:\n  enabled: false\n  minAvailable: 1\n# maxUnavailable: 1\n\nauthz:\n  # Disable if you don't want authorization.\n  # Mostly useful for debugging.\n  enabled: true\n  # Used for setting the mgmt token used for authz instead of auto generated default\n  # mgmtToken:\n  #    secretName: name of the secret\n  #    secretKey: (optional) key from the secret - default value is: \"mgmtToken\"\n\n# Use hostNetwork setting on OPA pod\nhostNetwork:\n  enabled: false\n\n# OPA docker image configuration.\nimage:\n  repository: openpolicyagent/opa\n  tag: 1.3.0\n  pullPolicy: IfNotPresent\n\n# One or more secrets to be used when pulling images\nimagePullSecrets: []\n# - registrySecretName\n\n# Should OPA use TLS or not.\nuseHttps: true\n\n# Port to which the opa pod will bind itself,\nport: 8181\n\nextraArgs: []\n\n# Extra environment variables to be loaded into the OPA container\nextraEnv: []\n\nmgmt:\n  enabled: true\n  image:\n    repository: openpolicyagent/kube-mgmt\n    tag: \"\" # appVersion is used by default, set to desired value to override\n    pullPolicy: IfNotPresent\n  extraArgs: []\n  extraEnv: []\n  resources: {}\n\n  # if empty - the current namespaces is watched\n  # if `*` - all namespaces are watched\n  namespaces: []\n\n  # kube-mgmt container will wait until OPA container comes to running state.\n  # Configure values for the startup probe, where kube-mgmt queries for the health\n  # of OPA container before it starts.\n  startupProbe:\n    failureThreshold: 5\n    httpGet:\n      path: /health\n      port: 8181 # Port on which OPA is configured\n      scheme: HTTPS\n    initialDelaySeconds: 20\n    successThreshold: 1\n    timeoutSeconds: 10\n\n  data:\n    enabled: true\n  policies:\n    enabled: true\n  # NOTE IF you use these, remember to update the RBAC rules below to allow\n  #      permissions to replicate these things\n  replicate:\n    cluster: []\n#     - [group/]version/resource\n    namespace: []\n#     - [group/]version/resource\n    path: kubernetes\n\n    ignoreNs: []\n\n    # Turn on auto-replication. kube-mgmt will apply OPA configuration file\n    # and analyze any configured bundles to determine which Kubernetes\n    # resources to replicate into OPA's in-memory store.\n    auto: false\n\n# Log level for OPA ('debug', 'info', 'error') (app default=info)\nlogLevel: info\n\n# Log format for OPA ('text', 'json') (app default=text)\nlogFormat: json\n\n# Number of OPA replicas to deploy. OPA maintains an eventually consistent\n# cache of policies and data. If you want high availability you can deploy two\n# or more replicas.\nreplicas: 1\n\n# To control how the OPA is scheduled on the cluster, set the affinity,\n# tolerations and nodeSelector values below. For example, to deploy OPA onto\n# the master nodes, 1 replica per node:\n#\n# affinity:\n#   podAntiAffinity:\n#     requiredDuringSchedulingIgnoredDuringExecution:\n#     - labelSelector:\n#         matchExpressions:\n#         - key: \"app\"\n#           operator: In\n#           values:\n#           - opa\n#       topologyKey: \"kubernetes.io/hostname\"\n# tolerations:\n# - key: \"node-role.kubernetes.io/master\"\n#   effect: NoSchedule\n#   operator: Exists\n# nodeSelector:\n#   kubernetes.io/role: \"master\"\naffinity: {}\ntolerations: []\nnodeSelector: {}\n\n# To control pod distribution across topology domains, set topologySpreadConstraints\n# below.\n#\n# topologySpreadConstraints:\n# - maxSkew: 1\n#   topologyKey: topology.kubernetes.io/zone\n#   whenUnsatisfiable: DoNotSchedule\n#   labelSelector:\n#     matchLabels:\n#       app: opa\ntopologySpreadConstraints: []\n\n# To control the CPU and memory resource limits and requests for OPA, set the\n# field below.\nresources: {}\n\nrbac:\n  # should ClusterRole for kube-mgmt be created\n  create: true\n  # extra rules to be added to a ClusterRole\n  extraRules: []\n    # - apiGroups: [\"\"]\n    #   resources: [\"configmaps\"]\n    #   verbs: [\"*\"]\n\nserviceAccount:\n  # Specifies whether a ServiceAccount should be created\n  create: true\n  # Annotations for the ServiceAccount\n  annotations: {}\n  # The name of the ServiceAccount to use.\n  # If not set and create is true, a name is generated using the fullname template\n  name:\n\n# This proxy allows opa to make Kubernetes SubjectAccessReview checks against the\n# Kubernetes API. You can get a rego function at github.com/open-policy-agent/library\nsar:\n  enabled: false\n  image:\n    repository: lachlanevenson/k8s-kubectl\n    tag: latest\n    pullPolicy: IfNotPresent\n  resources: {}\n\n# Set a priorityClass using priorityClassName\n# priorityClassName:\n\n# Timeout for a webhook call in seconds.\n# Starting in kubernetes 1.14 you can set the timeout and it is\n# encouraged to use a small timeout for webhooks. If the webhook call times out, the request\n# the request is handled according to the webhook'sfailure policy.\n# timeoutSeconds: 20\n\nsecurityContext:\n  enabled: false\n  runAsNonRoot: true\n  runAsUser: 1\n\ndeploymentStrategy: {}\n  # rollingUpdate:\n  #   maxSurge: 1\n  #   maxUnavailable: 0\n  # type: RollingUpdate\n\nextraContainers: []\n## Additional containers to be added to the opa pod.\n# - name: example-app\n#   image: example/example-app:latest\n#   args:\n#     - \"run\"\n#     - \"--port=11811\"\n#     - \"--config=/etc/example-app-conf/config.yaml\"\n#     - \"--opa-endpoint=https://localhost:443\"\n#   ports:\n#     - name: http\n#       containerPort: 11811\n#       protocol: TCP\n#   volumeMounts:\n#     - name: example-app-auth-config\n#       mountPath: /etc/example-app-conf\n\nextraVolumes: []\n## Additional volumes to the opa pod.\n# - name: example-app-auth-config\n#   secret:\n#     secretName: example-app-auth-config\n\nextraVolumeMounts: []\n## Mounting config for using the additional volumes\n#  - name: example-app-auth-config\n#    mountPath: /mount/path\n\nextraPorts: []\n## Additional ports to the opa services. Useful to expose extra container ports.\n# - port: 11811\n#   protocol: TCP\n#   name: http\n#   targetPort: http\n"
  },
  {
    "path": "cmd/kube-mgmt/flag.go",
    "content": "// Copyright 2017 The OPA Authors.  All rights reserved.\n// Use of this source code is governed by an Apache2\n// license that can be found in the LICENSE file.\n\npackage main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strings\"\n)\n\ntype groupVersionKind struct {\n\tGroup   string\n\tVersion string\n\tKind    string\n}\n\nvar errBadFormat = errors.New(\"format: group/version/kind\")\n\nfunc (gvk groupVersionKind) String() string {\n\tif gvk.Group != \"\" {\n\t\treturn fmt.Sprintf(\"%v/%v/%v\", gvk.Group, gvk.Version, gvk.Kind)\n\t}\n\treturn fmt.Sprintf(\"%v/%v\", gvk.Version, gvk.Kind)\n}\n\nfunc (gvk *groupVersionKind) Parse(value string) error {\n\tparts := strings.SplitN(value, \"/\", 3)\n\tfor i := range parts {\n\t\tif len(parts[i]) == 0 {\n\t\t\treturn errBadFormat\n\t\t}\n\t\tparts[i] = strings.ToLower(parts[i])\n\t}\n\tif len(parts) < 2 {\n\t\treturn errBadFormat\n\t}\n\tif len(parts) == 2 {\n\t\tgvk.Version = parts[0]\n\t\tgvk.Kind = parts[1]\n\t} else {\n\t\tgvk.Group = parts[0]\n\t\tgvk.Version = parts[1]\n\t\tgvk.Kind = parts[2]\n\t}\n\treturn nil\n}\n\ntype gvkFlag []groupVersionKind\n\nfunc (f *gvkFlag) String() string {\n\treturn fmt.Sprint(*f)\n}\n\nfunc (f *gvkFlag) Set(value string) error {\n\tvar gvk groupVersionKind\n\tif err := gvk.Parse(value); err != nil {\n\t\treturn err\n\t}\n\t*f = append(*f, gvk)\n\treturn nil\n}\n\nfunc (f *gvkFlag) Type() string {\n\treturn \"[group/]version/resource\"\n}\n"
  },
  {
    "path": "cmd/kube-mgmt/flag_test.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"reflect\"\n\t\"testing\"\n\n\t\"github.com/open-policy-agent/kube-mgmt/pkg/configmap\"\n\t\"github.com/spf13/cobra\"\n)\n\nfunc TestFlagParsing(t *testing.T) {\n\tvar f gvkFlag\n\n\tbadPaths := []string{\n\t\t\"foo/bar/\",\n\t\t\"foo\",\n\t}\n\n\tfor _, tc := range badPaths {\n\t\tif err := f.Set(tc); err == nil {\n\t\t\tt.Fatalf(\"Expected error from %v\", tc)\n\t\t}\n\t}\n\n\texpected := gvkFlag{\n\t\t{\"example.org\", \"foo\", \"bar\"},\n\t}\n\n\tif err := f.Set(\"example.org/Foo/bar\"); err != nil || !reflect.DeepEqual(expected, f) {\n\t\tt.Fatalf(\"Expected %v but got: %v (err: %v)\", expected, f, err)\n\t}\n\n\texpected = append(expected, groupVersionKind{\"example.org\", \"bar\", \"baz\"})\n\n\tif err := f.Set(\"example.org/Bar/baz\"); err != nil || !reflect.DeepEqual(expected, f) {\n\t\tt.Fatalf(\"Expected %v but got: %v (err: %v)\", expected, f, err)\n\t}\n\n\texpected = append(expected, groupVersionKind{\"\", \"v2\", \"corge\"})\n\n\tif err := f.Set(\"v2/corge\"); err != nil || !reflect.DeepEqual(expected, f) {\n\t\tt.Fatalf(\"Expected %v but got: %v (err: %v)\", expected, f, err)\n\t}\n\n}\n\nfunc TestFlagString(t *testing.T) {\n\n\tvar f gvkFlag\n\texpected := \"[example.org/foo/bar]\"\n\n\tif err := f.Set(\"example.org/foo/bar\"); err != nil || f.String() != expected {\n\t\tt.Fatalf(\"Exepcted %v but got: %v (err: %v)\", expected, f.String(), err)\n\t}\n}\n\nfunc TestPolicyFlags(t *testing.T) {\n\ttt := []struct {\n\t\tname           string\n\t\tflag           string\n\t\tvalue          string\n\t\texpectFullFlag string\n\t\terr            error\n\t}{\n\t\t{\n\t\t\tname:           \"valid\",\n\t\t\tflag:           \"openpolicyagent.org/policy\",\n\t\t\tvalue:          \"rego\",\n\t\t\texpectFullFlag: \"openpolicyagent.org/policy=rego\",\n\t\t\terr:            nil,\n\t\t},\n\t\t{\n\t\t\tname:           \"invalidFlag\",\n\t\t\tflag:           \"-foo\",\n\t\t\tvalue:          \"rego\",\n\t\t\texpectFullFlag: \"\",\n\t\t\terr:            errors.New(`key: Invalid value: \"-foo\": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName',  or 'my.name',  or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')`),\n\t\t},\n\t\t{\n\t\t\tname:           \"invalidValue\",\n\t\t\tflag:           \"foo\",\n\t\t\tvalue:          \"-rego\",\n\t\t\texpectFullFlag: \"\",\n\t\t\terr:            errors.New(`values[0][foo]: Invalid value: \"-rego\": a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyValue',  or 'my_value',  or '12345', regex used for validation is '(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?')`),\n\t\t},\n\t}\n\n\tfor _, tc := range tt {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trootCmd := &cobra.Command{\n\t\t\t\tUse:   \"test\",\n\t\t\t\tShort: \"test\",\n\t\t\t\tRunE: func(cmd *cobra.Command, args []string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t}\n\n\t\t\tvar params params\n\t\t\trootCmd.Flags().StringVarP(&params.policyLabel, \"policy-label\", \"\", \"\", \"replace label openpolicyagent.org/policy\")\n\t\t\trootCmd.Flags().StringVarP(&params.policyValue, \"policy-value\", \"\", \"\", \"replace value rego\")\n\n\t\t\trootCmd.SetArgs([]string{\"--policy-label=\" + tc.flag, \"--policy-value=\" + tc.value})\n\t\t\trootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {\n\t\t\t\tif rootCmd.Flag(\"policy-label\").Value.String() != \"\" || rootCmd.Flag(\"policy-value\").Value.String() != \"\" {\n\t\t\t\t\terr := configmap.CustomLabel(params.policyLabel, params.policyValue)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tif tc.err.Error() != err.Error() {\n\t\t\t\t\t\t\tt.Errorf(\"exp: %v\\ngot: %v\\n\", tc.err.Error(), err.Error())\n\t\t\t\t\t\t\tt.FailNow()\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trootCmd.Execute()\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "cmd/kube-mgmt/main.go",
    "content": "// Copyright 2017 The OPA Authors.  All rights reserved.\n// Use of this source code is governed by an Apache2\n// license that can be found in the LICENSE file.\n\npackage main\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"crypto/x509\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"path\"\n\t\"strings\"\n\n\t\"github.com/open-policy-agent/kube-mgmt/pkg/configmap\"\n\t\"github.com/open-policy-agent/kube-mgmt/pkg/data\"\n\t\"github.com/open-policy-agent/kube-mgmt/pkg/dynamicdata\"\n\t\"github.com/open-policy-agent/kube-mgmt/pkg/opa\"\n\t\"github.com/open-policy-agent/kube-mgmt/pkg/types\"\n\t\"github.com/open-policy-agent/kube-mgmt/pkg/version\"\n\n\t//lint:ignore SA1019 using OPA v0.x to ensure backwards compatible with pre-1.0 bundles\n\t\"github.com/open-policy-agent/opa/logging\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"github.com/spf13/cobra\"\n\t\"k8s.io/client-go/dynamic\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n)\n\ntype params struct {\n\tversion            bool\n\tkubeconfigFile     string\n\topaURL             string\n\topaAuth            string\n\topaAuthFile        string\n\topaCAFile          string\n\topaAllowInsecure   bool\n\tpolicyLabel        string\n\tpolicyValue        string\n\tdataLabel          string\n\tdataValue          string\n\tenablePolicies     bool\n\tenableData         bool\n\tnamespaces         []string\n\topaConfigFile      string\n\treplicateCluster   gvkFlag\n\treplicateNamespace gvkFlag\n\treplicatePath      string\n\tlogLevel           string\n\treplicateIgnoreNs  []string\n\tanalysisEntrypoint string\n\thealthEndpoint     string\n}\n\nfunc main() {\n\n\tvar params params\n\tcommandName := path.Base(os.Args[0])\n\n\trootCmd := &cobra.Command{\n\t\tUse:   commandName,\n\t\tShort: fmt.Sprintf(\"%v manages OPA on top of Kubernetes\", commandName),\n\t\tFParseErrWhitelist: cobra.FParseErrWhitelist{\n\t\t\tUnknownFlags: true,\n\t\t},\n\t\tRun: func(cmd *cobra.Command, args []string) {\n\t\t\tif params.version {\n\t\t\t\tfmt.Println(\"Version:\", version.Version)\n\t\t\t\tfmt.Println(\"Git:\", version.Git)\n\t\t\t} else {\n\t\t\t\trun(&params)\n\t\t\t}\n\t\t},\n\t}\n\n\t// Miscellaenous options.\n\trootCmd.Flags().BoolVarP(&params.version, \"version\", \"v\", false, \"print version and exit\")\n\trootCmd.Flags().StringVarP(&params.kubeconfigFile, \"kubeconfig\", \"\", \"\", \"set path to kubeconfig manually\")\n\trootCmd.Flags().StringVarP(&params.opaURL, \"opa-url\", \"\", \"http://localhost:8181/v1\", \"set URL of OPA API endpoint\")\n\trootCmd.Flags().StringVarP(&params.opaAuth, \"opa-auth-token\", \"\", \"\", \"set authentication token for OPA API endpoint\")\n\trootCmd.Flags().StringVarP(&params.opaAuthFile, \"opa-auth-token-file\", \"\", \"\", \"set file containing authentication token for OPA API endpoint\")\n\trootCmd.Flags().StringVarP(&params.opaCAFile, \"opa-ca-file\", \"\", \"\", \"set file containing certificate authority for OPA certificate\")\n\trootCmd.Flags().BoolVarP(&params.opaAllowInsecure, \"opa-allow-insecure\", \"\", false, \"allow insecure https connections to OPA\")\n\trootCmd.Flags().StringVar(&params.logLevel, \"log-level\", \"info\", \"set log level {debug, info, warn}\")\n\n\t// policy / data\n\trootCmd.Flags().BoolVarP(&params.enablePolicies, \"enable-policies\", \"\", true, \"whether to automatically discover policies from labelled ConfigMaps\")\n\trootCmd.Flags().StringVar(&params.policyLabel, \"policy-label\", \"openpolicyagent.org/policy\", \"label name for filtering ConfigMaps with policies\")\n\trootCmd.Flags().StringVar(&params.policyValue, \"policy-value\", \"rego\", \"label value for filtering ConfigMaps with policies\")\n\trootCmd.Flags().BoolVarP(&params.enableData, \"enable-data\", \"\", true, \"whether to automatically discover data from labelled ConfigMaps\")\n\trootCmd.Flags().StringVar(&params.dataLabel, \"data-label\", \"openpolicyagent.org/data\", \"label name for filtering ConfigMaps with data\")\n\trootCmd.Flags().StringVar(&params.dataValue, \"data-value\", \"opa\", \"label value for filtering ConfigMaps with data\")\n\trootCmd.Flags().StringSliceVarP(&params.namespaces, \"namespaces\", \"\", []string{\"\"}, \"namespaces to load policies and data from\")\n\n\t// replication\n\trootCmd.Flags().VarP(&params.replicateNamespace, \"replicate\", \"\", \"replicate namespace-level resources\")\n\trootCmd.Flags().VarP(&params.replicateCluster, \"replicate-cluster\", \"\", \"replicate cluster-level resources\")\n\trootCmd.Flags().StringVarP(&params.replicatePath, \"replicate-path\", \"\", \"kubernetes\", \"set path to replicate data into\")\n\trootCmd.Flags().StringSliceVarP(&params.replicateIgnoreNs, \"replicate-ignore-namespaces\", \"\", []string{\"\"}, \"namespaces that are ignored by replication\")\n\trootCmd.Flags().StringVarP(&params.opaConfigFile, \"opa-config\", \"\", \"\", \"set file containing OPA configuration to enable data replication based on configured bundles\")\n\trootCmd.Flags().StringVarP(&params.analysisEntrypoint, \"analysis-entrypoint\", \"\", \"main/main\", \"set decision to analyze for dynamic data replication configuration (requires --opa-config)\")\n\trootCmd.Flags().StringVarP(&params.healthEndpoint, \"health-endpoint\", \"\", \"\", \"set health check listening endpoint (e.g., localhost:8000)\")\n\n\trootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {\n\t\tif rootCmd.Flag(\"policy-label\").Value.String() != \"\" || rootCmd.Flag(\"policy-value\").Value.String() != \"\" {\n\t\t\terr := configmap.CustomLabel(params.policyLabel, params.policyValue)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.Fatalf(\"Invalid --policy-label:%v || --policy-value:%v, %v\", params.policyLabel, params.policyValue, err)\n\t\t\t}\n\t\t}\n\t\tif rootCmd.Flag(\"data-label\").Value.String() != \"\" || rootCmd.Flag(\"data-value\").Value.String() != \"\" {\n\t\t\terr := configmap.CustomLabel(params.dataLabel, params.dataValue)\n\t\t\tif err != nil {\n\t\t\t\tlogrus.Fatalf(\"Invalid --data-label:%v || --data-value:%v, %v\", params.dataLabel, params.dataValue, err)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\tif err := rootCmd.Execute(); err != nil {\n\t\tfmt.Fprintln(os.Stderr, err)\n\t\tos.Exit(1)\n\t}\n\n}\n\nfunc run(params *params) {\n\n\tswitch params.logLevel {\n\tcase \"debug\":\n\t\tlogrus.SetLevel(logrus.DebugLevel)\n\tcase \"info\":\n\t\tlogrus.SetLevel(logrus.InfoLevel)\n\tcase \"warn\":\n\t\tlogrus.SetLevel(logrus.WarnLevel)\n\tdefault:\n\t\tlogrus.Fatalf(\"Invalid log level %v\", params.logLevel)\n\t}\n\n\tkubeconfig, err := loadRESTConfig(params.kubeconfigFile)\n\tif err != nil {\n\t\tlogrus.Fatalf(\"Failed to load kubeconfig: %v\", err)\n\t}\n\n\tif params.opaAuthFile != \"\" && params.opaAuth != \"\" {\n\t\tlogrus.Fatalf(\"You can not use both --opa-auth-token and --opa-auth-token-file\")\n\t}\n\n\tif params.opaAuthFile != \"\" {\n\t\tfile, err := os.ReadFile(params.opaAuthFile)\n\t\tif err != nil {\n\t\t\tlogrus.Fatalf(\"Failed to read opa auth token file %s\", params.opaAuthFile)\n\t\t}\n\t\tparams.opaAuth = strings.Split(string(file), \"\\n\")[0]\n\t}\n\n\tif params.opaAllowInsecure && params.opaCAFile != \"\" {\n\t\tlogrus.Fatalf(\"You can not use both --opa-allow-insecure and --opa-ca-file\")\n\t}\n\n\tif params.opaAllowInsecure {\n\t\tconfig := &tls.Config{InsecureSkipVerify: params.opaAllowInsecure}\n\t\thttp.DefaultTransport.(*http.Transport).TLSClientConfig = config\n\t}\n\n\tif params.opaCAFile != \"\" {\n\t\trootCAs, _ := x509.SystemCertPool()\n\t\tif rootCAs == nil {\n\t\t\trootCAs = x509.NewCertPool()\n\t\t}\n\t\tcerts, err := os.ReadFile(params.opaCAFile)\n\t\tif err != nil {\n\t\t\tlogrus.Fatalf(\"Failed to read opa certificate authority file %s\", params.opaCAFile)\n\t\t}\n\t\tif ok := rootCAs.AppendCertsFromPEM(certs); !ok {\n\t\t\tlogrus.Println(\"No certs appended, using system certs only\")\n\t\t}\n\t\tconfig := &tls.Config{RootCAs: rootCAs}\n\t\thttp.DefaultTransport.(*http.Transport).TLSClientConfig = config\n\t}\n\n\tif params.enablePolicies || params.enableData {\n\t\tsync := configmap.New(\n\t\t\tkubeconfig,\n\t\t\topa.New(params.opaURL, params.opaAuth),\n\t\t\tconfigmap.DefaultConfigMapMatcher(\n\t\t\t\tparams.namespaces,\n\t\t\t\tparams.enablePolicies,\n\t\t\t\tparams.enableData,\n\t\t\t\tparams.policyLabel,\n\t\t\t\tparams.policyValue,\n\t\t\t\tparams.dataLabel,\n\t\t\t\tparams.dataValue,\n\t\t\t),\n\t\t)\n\t\t_, err = sync.Run(params.namespaces)\n\t\tif err != nil {\n\t\t\tlogrus.Fatalf(\"Failed to start configmap sync: %v\", err)\n\t\t}\n\t}\n\n\tif len(params.replicateCluster)+len(params.replicateNamespace) > 0 {\n\t\tclient, err := dynamic.NewForConfig(kubeconfig)\n\t\tif err != nil {\n\t\t\tlogrus.Fatalf(\"Failed to get dynamic client: %v\", err)\n\t\t}\n\n\t\tctx, cancel := context.WithCancel(context.Background())\n\t\tdefer cancel()\n\n\t\topts := data.WithIgnoreNamespaces(params.replicateIgnoreNs)\n\n\t\tfor _, gvk := range params.replicateCluster {\n\t\t\tsync := data.NewFromInterface(client, opa.New(params.opaURL, params.opaAuth).Prefix(params.replicatePath), getResourceType(gvk, false), opts)\n\t\t\tgo sync.RunContext(ctx)\n\t\t}\n\n\t\tfor _, gvk := range params.replicateNamespace {\n\t\t\tsync := data.NewFromInterface(client, opa.New(params.opaURL, params.opaAuth).Prefix(params.replicatePath), getResourceType(gvk, true), opts)\n\t\t\tgo sync.RunContext(ctx)\n\t\t}\n\t}\n\n\tvar sync *dynamicdata.Sync\n\n\tif params.opaConfigFile != \"\" {\n\t\tlogger := logging.New()\n\t\tswitch params.logLevel {\n\t\tcase \"debug\":\n\t\t\tlogger.SetLevel(logging.Debug)\n\t\tcase \"info\":\n\t\t\tlogger.SetLevel(logging.Info)\n\t\tcase \"error\":\n\t\t\tlogger.SetLevel(logging.Error)\n\t\t}\n\t\tsync, err = dynamicdata.New(params.opaConfigFile, params.analysisEntrypoint, params.opaURL, params.opaAuth, params.replicateIgnoreNs, params.replicatePath, kubeconfig, logger)\n\t\tif err != nil {\n\t\t\tlogrus.Fatalf(\"Failed to create dynamic synchronizer: %v\", err)\n\t\t}\n\t\tgo sync.Run(context.Background())\n\t}\n\n\tif params.healthEndpoint != \"\" {\n\t\tgo func() {\n\t\t\tmux := http.NewServeMux()\n\t\t\tmux.HandleFunc(\"/health\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\t\tif sync == nil || sync.Ready() {\n\t\t\t\t\tlogrus.Debugf(\"health check: READY\")\n\t\t\t\t\tw.WriteHeader(http.StatusOK)\n\t\t\t\t} else {\n\t\t\t\t\tlogrus.Debugf(\"health check: NOT READY\")\n\t\t\t\t\tw.WriteHeader(http.StatusInternalServerError)\n\t\t\t\t}\n\t\t\t})\n\t\t\tserver := &http.Server{\n\t\t\t\tAddr:    params.healthEndpoint,\n\t\t\t\tHandler: mux,\n\t\t\t}\n\n\t\t\tlogrus.Infof(\"Starting health server on %v\", params.healthEndpoint)\n\t\t\tif err := server.ListenAndServe(); err != nil {\n\t\t\t\tlogrus.Fatalf(\"Error starting health server: %v\", err)\n\t\t\t}\n\t\t}()\n\t}\n\n\tquit := make(chan struct{})\n\t<-quit\n}\n\nfunc loadRESTConfig(path string) (*rest.Config, error) {\n\tif path != \"\" {\n\t\treturn clientcmd.BuildConfigFromFlags(\"\", path)\n\t}\n\treturn rest.InClusterConfig()\n}\n\nfunc getResourceType(gvk groupVersionKind, namespaced bool) types.ResourceType {\n\treturn types.ResourceType{\n\t\tNamespaced: namespaced,\n\t\tGroup:      gvk.Group,\n\t\tVersion:    gvk.Version,\n\t\tResource:   gvk.Kind,\n\t}\n}\n"
  },
  {
    "path": "devbox.json",
    "content": "{\n  \"$schema\": \"https://raw.githubusercontent.com/jetify-com/devbox/0.13.7/.schema/devbox.schema.json\",\n  \"packages\": [\n    \"go@1.24\",\n    \"just@1\",\n    \"kubectl@1.33\",\n    \"k3d@5.8\",\n    \"kubernetes-helm@3\",\n    \"go-tools@2026\",\n    \"open-policy-agent@1\",\n    \"hurl@7\",\n    \"fzf@0\",\n    \"devspace@6\",\n    \"yq-go@4\",\n    \"ko@0\",\n    \"oras@1\"\n  ],\n  \"env\": {\n    \"GOPATH\": \"$HOME/go/\",\n    \"PATH\":   \"$PATH:$HOME/go/bin\"\n  },\n  \"shell\": {\n    \"init_hook\": [\n      \"command -v chainsaw &>/dev/null || go install github.com/kyverno/chainsaw@v0.2.13\",\n      \"helm plugin list | grep -q unittest || helm plugin install https://github.com/helm-unittest/helm-unittest --version v1.0.3\"\n    ]\n  }\n}\n"
  },
  {
    "path": "devspace.yaml",
    "content": "version: v2beta1\nname: opa-kube-mgmt\n\nvars:\n  DEVSPACE_FLAGS: \"-n default --no-warn\"\n  KO_PLATFORMS: \"linux/amd64\"\n  KO_EXTRA_TAGS: \"\"\n\nimages:\n  default:\n    image: localhost:5001/openpolicyagent/kube-mgmt\n    tags:\n      - $(git describe --tags --always --dirty)\n    custom:\n      command: |\n        export KO_DOCKER_REPO=${runtime.images.default.image}\n        export KO_VERSION=${runtime.images.default.tag}\n        export KO_COMMIT=${DEVSPACE_GIT_COMMIT}\n        ko build --bare --tags ${KO_VERSION}${KO_EXTRA_TAGS} --platform=${KO_PLATFORMS} ./cmd/kube-mgmt\n\ndeployments:\n  default:\n    namespace: default\n    helm:\n      releaseName: ${DEVSPACE_NAME}\n      chart:\n        path: charts/${DEVSPACE_NAME}\n      values:\n        e2e: true\n        mgmt:\n          image:\n            repository: ${runtime.images.default.image}\n            tag: ${runtime.images.default.tag}\n      valuesFiles:\n        - \"${E2E_TEST}/values.yaml\"\n      upgradeArgs:\n        - \"--wait\"\n        - \"--install\"\n\nhooks:\n  - name: \"helm package and copy to ghcr\"\n    events: [\"after:build:default\"]\n    disabled: true\n    command: |\n      helm package charts/${DEVSPACE_NAME} \\\n        --version ${runtime.images.default.tag} --app-version ${runtime.images.default.tag}\n      helm push ${DEVSPACE_NAME}-${runtime.images.default.tag}.tgz oci://ghcr.io/open-policy-agent/helm\n  - name: \"copy docker image to ghcr\"\n    events: [\"after:build:default\"]\n    disabled: true\n    command: |\n      oras cp docker.io/${runtime.images.default.image}:${runtime.images.default.tag} \\\n        ghcr.io/open-policy-agent/docker/${DEVSPACE_NAME}:${runtime.images.default.tag},latest\n  - name: \"e2e cleanup\"\n    events: [\"before:deploy:default\"]\n    command: |\n      kubectl delete cm -l kube-mgmt/e2e=true -n ${DEVSPACE_NAMESPACE} --ignore-not-found\n      kubectl delete svc -l kube-mgmt/e2e=true -n ${DEVSPACE_NAMESPACE} --ignore-not-found\n\nprofiles:\n  - name: release\n    patches:\n      - op: replace\n        path: images.default.image\n        value: openpolicyagent/kube-mgmt\n      - op: replace\n        path: vars.KO_PLATFORMS\n        value: \"linux/amd64,linux/arm64\"\n      - op: replace\n        path: vars.KO_EXTRA_TAGS\n        value: \",latest\"\n      - op: replace\n        path: hooks[0].disabled\n        value: false\n      - op: replace\n        path: hooks[1].disabled\n        value: false\n"
  },
  {
    "path": "docs/admission-control-1.7.md",
    "content": "# Admission Control (1.7 and 1.8)\n\n**Note: Admission Control has undergone changes in Kubernetes 1.7 through 1.9. If you are running Kubernetes 1.9, see [Kubernetes Admission Control](http://www.openpolicyagent.org/docs/kubernetes-admission-control.html) instead.**\n\nTo use OPA as an [Admission Controller](https://kubernetes.io/docs/admin/admission-controllers/#what-are-they) in Kubernetes 1.7 or 1.8, follow the steps in [External Admission Webhooks](https://kubernetes.io/docs/admin/extensible-admission-controllers/#external-admission-webhooks) to enable webhooks in the Kubernetes API server. Once you have configured the Kubernetes API server and generated the necessary certificates you can start `kube-mgmt` with the following options:\n\n```bash\n--register-admission-controller\n--admission-controller-ca-cert-file=/path/to/ca/cert.pem\n--admission-controller-service-name=<name-of-opa-service>\n--admission-controller-service-namespace=<namespace-of-opa-service>\n```\n\nIn addition to the command line arguments above, you must provide `--pod-name` and `--pod-namespace` using [Kubernetes' Downward API](https://kubernetes.io/docs/tasks/inject-data-application/downward-api-volume-expose-pod-information/). The example manifest below shows how to set these.\n\nYou will need to create Secrets containing the server certificate and private key as well as the CA certificate:\n\n```bash\nkubectl create secret generic opa-ca --from-file=ca.crt\nkubectl create secret tls opa-server --cert=server.crt --key=server.key\n```\n\n> See [Generating TLS Certificates](./tls-1.7.md) below for examples of how to generate the certificate files.\n\nThe example below shows how to deploy OPA and enable admission control:\n\n```yaml\napiVersion: extensions/v1beta1\nkind: Deployment\nmetadata:\n  labels:\n    app: opa\n  name: opa\nspec:\n  replicas: 1\n  template:\n    metadata:\n      labels:\n        app: opa\n      name: opa\n    spec:\n      containers:\n        - name: opa\n          image: openpolicyagent/opa\n          args:\n            - \"run\"\n            - \"--server\"\n            - \"--tls-cert-file=/certs/tls.crt\"\n            - \"--tls-private-key-file=/certs/tls.key\"\n            - \"--addr=0.0.0.0:443\"\n            - \"--insecure-addr=127.0.0.1:8181\"\n          volumeMounts:\n            - readOnly: true\n              mountPath: /certs\n              name: opa-server\n        - name: kube-mgmt\n          image: openpolicyagent/kube-mgmt:0.6\n          args:\n            - \"--pod-name=$(MY_POD_NAME)\"\n            - \"--pod-namespace=$(MY_POD_NAMESPACE)\"\n            - \"--register-admission-controller\"\n            - \"--admission-controller-ca-cert-file=/certs/ca.crt\"\n            - \"--admission-controller-service-name=opa\"\n            - \"--admission-controller-service-namespace=$(MY_POD_NAMESPACE)\"\n          volumeMounts:\n            - readOnly: true\n              mountPath: /certs\n              name: opa-ca\n          env:\n            - name: MY_POD_NAME\n              valueFrom:\n                fieldRef:\n                  fieldPath: metadata.name\n            - name: MY_POD_NAMESPACE\n              valueFrom:\n                fieldRef:\n                  fieldPath: metadata.namespace\n      volumes:\n        - name: opa-server\n          secret:\n            secretName: opa-server\n        - name: opa-ca\n          secret:\n            secretName: opa-ca\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: opa\nspec:\n  clusterIP: 10.0.0.222\n  selector:\n    app: opa\n  ports:\n  - name: https\n    protocol: TCP\n    port: 443\n    targetPort: 443\n```\n\nAdmission control policies must produce a document at `/system/main` that\nrepresents the admission control decision (i.e., allow or deny).\n\n#### Example Policy\n\nTo test that admission control is working, define a policy that rejects the\nrequest if the `test-reject` label is found:\n\n```ruby\npackage system\n\nmain = {\n  \"apiVersion\": \"admission.k8s.io/v1alpha1\",\n  \"kind\": \"AdmissionReview\",\n  \"status\": status,\n}\n\ndefault status = {\"allowed\": true}\n\nstatus = reject {\n  input.spec.operation = \"CREATE\"\n  input.spec.object.labels[\"test-reject\"]\n}\n\nreject = {\n  \"allowed\": false,\n  \"status\": {\n    \"reason\": \"testing rejection\"\n  }\n}\n```\n\n"
  },
  {
    "path": "docs/admission-control-crd.md",
    "content": "# Admission Control For Custom Resources\n\nIn the [Kubernetes Admission Control](http://www.openpolicyagent.org/docs/kubernetes-admission-control.html) tutorial we have seen how OPA can be deployed as an admission controller to enforce custom policies on Kubernetes objects. In that tutorial, policies were enforced on native Kubernetes objects such as ingresses.\n\n## Goal\n\nThis tutorial will show how OPA can be used to enforce polices on custom resources. A custom resource is an extension of the Kubernetes API that is not necessarily available on every Kubernetes cluster. More inforation on Kubernetes custom resources is available [here](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/).\n\nThe additional steps that need to be taken to achieve this are:\n\n1. Define a role for reading Kubernetes custom resources.\n2. Grant OPA/kube-mgmt permissions to read Kubernetes custom resources.\n3. Configure `kube-mgmt` to load Kubernetes custom resources into OPA.\n\n## Prerequisites\n\nSame as the [Kubernetes Admission Control](http://www.openpolicyagent.org/docs/kubernetes-admission-control.html) tutorial.\n\n## Steps\n\n### 1. Start minikube\n\n```bash\nminikube start\n```\n\nFollow the steps in the [Kubernetes Admission Control](http://www.openpolicyagent.org/docs/kubernetes-admission-control.html) tutorial to create the `opa` namespace and configure TLS.\n\n### 2. Create a CustomResourceDefinition\n\nSave the following CustomResourceDefinition to **resourcedefinition.yaml**:\n\n```yaml\napiVersion: apiextensions.k8s.io/v1beta1\nkind: CustomResourceDefinition\nmetadata:\n  name: cats.opa.example.com\nspec:\n  group: opa.example.com\n  version: \"v1\"\n  scope: Namespaced\n  names:\n    plural: cats\n    singular: cat\n    kind: Cat\n    shortNames:\n    - ct\n```\n\nAnd create it:\n\n```bash\nkubectl create -f resourcedefinition.yaml\n```\n\n### 3. Deploy OPA on top of Kubernetes\n\nUse the **admission-controlller.yaml** file from the [Kubernetes Admission Control](http://www.openpolicyagent.org/docs/kubernetes-admission-control.html) tutorial to deploy OPA as an admission controller with the following changes:\n\n1. Define a role for reading the Kubernetes custom resource created in the previous step.\n\n```yaml\nkind: ClusterRole\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: crd-reader\nrules:\n- apiGroups: [\"opa.example.com\"]\n  resources: [\"cats\"]\n  verbs: [\"get\", \"list\", \"watch\"]\n```\n\n2. Grant OPA/kube-mgmt permissions to the above role.\n\n```yaml\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: opa-crd-reader\nroleRef:\n  kind: ClusterRole\n  name: crd-reader\n  apiGroup: rbac.authorization.k8s.io\nsubjects:\n- kind: Group\n  name: system:serviceaccounts:opa\n  apiGroup: rbac.authorization.k8s.io\n```\n\n3. Update the `kube-mgmt` container spec to load the Kubernetes custom resources into OPA.\n\n```yaml\nname: kube-mgmt\nargs:\n    - \"--replicate=opa.example.com/v1/cats\"        # replicate custom resources\n```\n\nNow follow the [Kubernetes Admission Control](http://www.openpolicyagent.org/docs/kubernetes-admission-control.html) tutorial to deploy OPA on top of Kubernetes and register OPA as an admission controller.\n\n### 4. Define a policy and load it into OPA via Kubernetes\n\nCreate a policy that rejects objects of kind `Cat` from sharing the same cat name.\n\n**name-conflicts.rego**:\n\n```ruby\npackage kubernetes.admission\n\nimport data.kubernetes.cats\n\n# Cat names must be unique.\ndeny[msg] {\n    input.request.kind.kind = \"Cat\"\n    input.request.operation = \"CREATE\"\n    name := input.request.object.spec.name\n    cat := cats[other_ns][other_cat]\n    cat.spec.name == name\n    msg = sprintf(\"duplicate cat name %q (conflicts with %v/%v)\", [name, other_ns, other_cat])\n}\n```\n\n```bash\nkubectl create configmap name-conflicts --from-file=name-conflicts.rego\n```\n\n### 5. Exercise the policy\n\nDefine two objects of kind `Cat`. The first one will be permitted and the second will be rejected.\n\n**cat.yaml**:\n\n```yaml\napiVersion: \"opa.example.com/v1\"\nkind: Cat\nmetadata:\n  name: my-new-cat-object\nspec:\n  name: Whiskers\n```\n\n**cat-duplicate.yaml**:\n\n```yaml\napiVersion: \"opa.example.com/v1\"\nkind: Cat\nmetadata:\n  name: my-duplicate-cat-object\nspec:\n  name: Whiskers\n```\n\nFinally, try to create both `Cat` objects:\n\n```bash\nkubectl create -f cat.yaml\nkubectl create -f cat-duplicate.yaml\n```\n\nThe second object will be rejected since an object with the cat name `Whiskers` was created earlier.\n"
  },
  {
    "path": "docs/admission-control-secure.md",
    "content": "# Admission Control Secure\n\nIn the [Kubernetes Admission Control](http://www.openpolicyagent.org/docs/kubernetes-admission-control.html) tutorial we have seen\nhow OPA can be deployed as an admission controller. In that tutorial, OPA is not configured to `authenticate` and `authorize` client requests.\n\n## Goal\n\nThis tutorial will show how to securely deploy OPA as an admission controller. The additional steps that need to be taken to achieve this are:\n\n1. Start `OPA` with authentication and authorization enabled using the `--authentication` and `--authorization` options respectively.\n2. Volume mount OPA's startup authorization policy into the OPA container.\n3. Start `kube-mgmt` with `Bearer` token flag using the `--opa-auth-token-file` option.\n4. Configure `kube-mgmt` to load polices stored in ConfigMaps that are created in the `opa` namespace and are labelled `openpolicyagent.org/policy=rego`.\n5. Configure the Kubernetes API server to use `Bearer` token.\n\n\n## Prerequisites\n\nSame as the [Kubernetes Admission Control](http://www.openpolicyagent.org/docs/kubernetes-admission-control.html) tutorial.\n\n## Steps\n\n### 1. Configure Kubernetes API server\n\nOPA will `authenticate` clients by extracting the `Bearer` token from the incoming API requests. Hence the Kubernetes API server needs to be configured\nto send a `Bearer` token in all requests to OPA. To do this, the API server must be provided with an admission control configuration file via the `--admission-control-config-file`\nflag during startup. This means the configuration file should be present inside the minikube VM at a location which is accessible to the API server pod.\n\nStart minikube:\n\n```bash\nminikube start\n```\n\n`ssh` into the minikube VM and place the configuration files (**admission-control-config.yaml** and **kube-config.yaml**) below\ninside `/var/lib/minikube/certs`. This directory is accessible inside the API server pod.\n\n**admission-control-config.yaml**\n\n```yaml\napiVersion: apiserver.k8s.io/v1alpha1\nkind: AdmissionConfiguration\nplugins:\n- name: ValidatingAdmissionWebhook\n  configuration:\n    apiVersion: apiserver.config.k8s.io/v1alpha1\n    kind: WebhookAdmission\n    kubeConfigFile: /var/lib/minikube/certs/kube-config.yaml\n```\n\n**kube-config.yaml**\n\n```yaml\napiVersion: v1\nkind: Config\nusers:\n# '*' is the default match.\n- name: '*'\n  user:\n    token: <apiserver_secret_token>\n```\n\nWith the above configuration, all requests the API server makes to OPA will include a `Bearer` token. You will need to generate the `Bearer`\ntoken (`<apiserver_secret_token>`) and later include it in OPA's startup authorization policy so that OPA can verify the identity of the API server.\n\nNow exit the minikube VM and stop it:\n\n```bash\nminikube stop\n```\n\nStart minikube by passing information about the admission control configuration file to the API server:\n\n```bash\nminikube start --extra-config=apiserver.admission-control-config-file=/var/lib/minikube/certs/admission-control-config.yaml\n```\n\nMake sure that the minikube ingress addon is enabled:\n\n```bash\nminikube addons enable ingress\n```\n\nFollow the steps in the [Kubernetes Admission Control](http://www.openpolicyagent.org/docs/kubernetes-admission-control.html) tutorial to create the `opa`\nnamespace and configure TLS. Now use the **admission-controller.yaml** file from the tutorial to deploy OPA as an admission controller with the following changes:\n\n1. Use the below `opa` and `kube-mgmt` container spec which enables OPA's security features and configures `kube-mgmt` to include a `Bearer` token\nin calls to OPA. We also volume mount OPA's startup authorization policy `authz.rego` inside the OPA container in the `/policies` directory.\n\n```yaml\nspec:\n  containers:\n    - name: opa\n      image: openpolicyagent/opa:0.42.1\n      args:\n        - \"run\"\n        - \"--server\"\n        - \"--tls-cert-file=/certs/tls.crt\"\n        - \"--tls-private-key-file=/certs/tls.key\"\n        - \"--addr=0.0.0.0:443\"\n        - \"--addr=http://127.0.0.1:8181\"\n        - \"--authentication=token\"\n        - \"--authorization=basic\"\n        - \"/policies/authz.rego\" # authorization policy used on startup\n        - \"--ignore=.*\"          # exclude hidden dirs created by Kubernetes\n      volumeMounts:\n        - readOnly: true\n          mountPath: /certs\n          name: opa-server\n        - readOnly: true\n          mountPath: /policies\n          name: inject-policy\n    - name: kube-mgmt\n      image: openpolicyagent/kube-mgmt:7.0.6\n      args:\n        - \"--replicate-cluster=v1/namespaces\"\n        - \"--replicate=extensions/v1beta1/ingresses\"\n        - \"--opa-auth-token-file=/policies/token\"\n      volumeMounts:\n        - readOnly: true\n          mountPath: /policies\n          name: inject-policy\n  volumes:\n    - name: opa-server\n      secret:\n        secretName: opa-server\n    - name: inject-policy\n      secret:\n        secretName: inject-policy\n```\n\n2. Include the Secret that contains OPA's startup authorization policy.\n\n```bash\ncat > authz.rego <<EOF\npackage system.authz\n\ndefault allow = false\n\nallow {\n  \"kube-mgmt\" = input.identity\n}\n\nallow {\n  <apiserver_secret_token> = input.identity\n}\nEOF\n\nkubectl create secret generic inject-policy -n opa --from-file=authz.rego --from-literal=token=kube-mgmt\n\n```\n\nIf you have liveness or readiness probes configured on the OPA server for `/health` you will need to add the following `allow` rule\nto ensure Kubernetes can still access these endpoints.\n\n```\n# Allow anonymouse access to /health otherwise K8s get 403 and kills pod.\nallow {\n    input.path = [\"health\"]\n}\n```\n\n3. Label the `opa-default-system-main` ConfigMap.\n\n```yaml\n---\nkind: ConfigMap\napiVersion: v1\n\nmetadata:\n  name: opa-default-system-main\n  namespace: opa\n  labels:\n    openpolicyagent.org/policy: rego\ndata:\n  main: |\n    package system\n\n    import data.kubernetes.admission\n\n    main = {\n      \"apiVersion\": \"admission.k8s.io/v1beta1\",\n      \"kind\": \"AdmissionReview\",\n      \"response\": response,\n    }\n\n    default response = {\"allowed\": true}\n\n    response = {\n        \"allowed\": false,\n        \"status\": {\n            \"reason\": reason,\n        },\n    } {\n        reason = concat(\", \", admission.deny)\n        reason != \"\"\n    }\n```\n\nWhen OPA starts, the `kube-mgmt` container will load Kubernetes Namespace and Ingress objects into OPA. `kube-mgmt` will automatically\ndiscover policies stored in ConfigMaps in Kubernetes and load them into OPA. `kube-mgmt` assumes a ConfigMap contains policies if the ConfigMap is:\n\n- Created in a namespace listed in the `--namespaces` option. Default namespace is `opa`.\n- Labelled with `openpolicyagent.org/policy=rego`.\n\n`kube-mgmt` is started with the `--opa-auth-token-file` flag and hence all requests made to OPA will include a `Bearer` token(`kube-mgmt` in this case).\n\nYou can now follow the [Kubernetes Admission Control](http://www.openpolicyagent.org/docs/kubernetes-admission-control.html)\ntutorial to deploy OPA on top of Kubernetes and test admission control. **Make sure to label the ConfigMap when you store a policy inside it.**\n"
  },
  {
    "path": "docs/tls-1.7.md",
    "content": "# Generating TLS Certificates (1.7)\n\nExternal Admission Controllers must be secured with TLS. At a minimum you must:\n\n- Provide the Kubernetes API server with a client key to use for\n  webhook calls (`client.key` and `client.crt` below).\n\n- Provide OPA with a server key so that the Kubernetes API server can\n  authenticate it (`server.key` and `server.crt` below).\n\n- Provide `kube-mgmt` with the CA certificate to register with the Kubernetes\n  API server (`ca.crt` below).\n\nFollow the steps below to generate the necessary files for test purposes.\n\nFirst, generate create the required OpenSSL configuration files:\n\n**client.conf**:\n\n```\n[req]\nreq_extensions = v3_req\ndistinguished_name = req_distinguished_name\n[req_distinguished_name]\n[ v3_req ]\nbasicConstraints = CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment\nextendedKeyUsage = clientAuth, serverAuth\nsubjectAltName = @alt_names\n[alt_names]\nIP.1 = 127.0.0.1\n```\n\n**server.conf**:\n\n```\n[req]\nreq_extensions = v3_req\ndistinguished_name = req_distinguished_name\n[req_distinguished_name]\n[ v3_req ]\nbasicConstraints = CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment\nextendedKeyUsage = clientAuth, serverAuth\nsubjectAltName = @alt_names\n[alt_names]\nIP.1 = 10.0.0.222\n```\n\n> The subjectAltName/IP address in the certificate MUST match the one configured\n> on the Kubernetes Service.\n\nFinally, generate the CA and client/server key pairs.\n\n```bash\n# Create a certificate authority\nopenssl genrsa -out ca.key 2048\nopenssl req -x509 -new -nodes -key ca.key -days 100000 -out ca.crt -subj \"/CN=admission_ca\"\n\n# Create a server certiticate\nopenssl genrsa -out server.key 2048\nopenssl req -new -key server.key -out server.csr -subj \"/CN=admission_server\" -config server.conf\nopenssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 100000 -extensions v3_req -extfile server.conf\n\n# Create a client certiticate\nopenssl genrsa -out client.key 2048\nopenssl req -new -key client.key -out client.csr -subj \"/CN=admission_client\" -config client.conf\nopenssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 100000 -extensions v3_req -extfile client.conf\n```\n\nIf you are using minikube, you can specify the client TLS credentials with the following `minikube start` options:\n\n```\n--extra-config=apiserver.ProxyClientCertFile=/path/to/client.crt  # in VM\n--extra-config=apiserver.ProxyClientKeyFile=/path/to/client.key   # in VM\n```"
  },
  {
    "path": "examples/service_validation/README.md",
    "content": "# Kubernetes Admission Control for preventing open AWS LoadBalancers\n\nKubernetes Service objects of type [LoadBalancer](https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/) on AWS create an Elastic LoadBalancer or Network LoadBalancer. However, if not properly configured, these LoadBalancers can be open to the world exposing EC2 instances behind them to security breaches.\n\nOPA can provide a good ValidationWebhook for ensuring that Service objects of type LoadBalancer do not accidentally create a LoadBalancer open to the world.\n\n## Goals\n\nThis tutorial shows how to create validation webhooks for Service objects and enforcing the LoadBalancer policies.\n\n- Kubernetes Service objects of type LoadBalancer that do not have `spec.loadBalancerSourceRanges` are rejected.\n- Users are required to explicitly set `spec.loadBalancerSourceRanges`. If users want to create LoadBalancers that are actually open to the world, they should explicitly set `spec.loadBalancerSourceRanges` to `0.0.0.0/0`.\n\n## Prerequisites\n\nThis tutorial has been tested with Kubernetes 1.10 running on AWS with RBAC enabled. But it should work with Kubernetes 1.9 or higher.\n\n## Steps\n\n### The simplest way to setup opa and policies would be to run the install.sh script.\n\n```bash\n$ ./install.sh\n```\n\nOtherwise, here are the detailed steps:\n\n### 1. Start Kubernetes with ValidatingAdmissionWebhook admission controller enabled.\n\n### 2. Create the namespace called `opa` in it.\n\n```bash\nkubectl create namespace opa\n```\n\n### 3. Create the SSL certs required for the webhook. Same as [this](https://github.com/open-policy-agent/opa/blob/master/docs/book/kubernetes-admission-control.md#3-deploy-opa-on-top-of-kubernetes)\n\n```bash\nopenssl genrsa -out ca.key 2048\nopenssl req -x509 -new -nodes -key ca.key -days 100000 -out ca.crt -subj \"/CN=admission_ca\"\n```\n\nGenerate the TLS key and certificate for OPA:\n\n```bash\ncat >server.conf <<EOF\n[req]\nreq_extensions = v3_req\ndistinguished_name = req_distinguished_name\n[req_distinguished_name]\n[ v3_req ]\nbasicConstraints = CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment\nextendedKeyUsage = clientAuth, serverAuth\nEOF\n```\n\n```bash\nopenssl genrsa -out server.key 2048\nopenssl req -new -key server.key -out server.csr -subj \"/CN=opa.opa.svc\" -config server.conf\nopenssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 100000 -extensions v3_req -extfile server.conf\n```\n\n> Note: the Common Name value you give to openssl MUST match the name of the OPA service created below.\n\nCreate a Secret to store the TLS credentials for OPA:\n\n```bash\nkubectl create secret tls opa-server --cert=server.crt --key=server.key\n```\n\nIn the admission_controller.yaml file in this example, replace the REPLACE_WITH_SECRET with the base64 encoded \n\n```bash\nkubectl apply -f ./examples/service_validation/admission-controller.yaml\n```\n\nThis creates the OPA deployment, the validation webhook as well as the config map which has the policy.\n\n### 4. Exercise the policy\n\nCreate a service object and ensure that it is enforcing the policy.\n\n**service_invalid.yaml**:\n\n```yaml\napiVersion: v1\nkind: Service\nmetadata:\n  name: no-whitelist-ips\nspec:\n  ports:\n  - port: 80\n    protocol: TCP\n    targetPort: 80\n  selector:\n    run: nginx\n  type: LoadBalancer\n```\n\n**service_valid.yaml**:\n\n```yaml\napiVersion: v1\nkind: Service\nmetadata:\n  name: whitelist-ips\nspec:\n  ports:\n  - port: 80\n    protocol: TCP\n    targetPort: 80\n  selector:\n    run: nginx\n  type: LoadBalancer\n  loadBalancerSourceRanges:\n  - 10.0.0.0/8\n```\n\n```bash\nkubectl create -f service_invalid.yaml\nkubectl create -f service_valid.yaml\n```\n\nThis tutorial showed how you can leverage OPA to enforce admission control of Service objects to prevent accidentally exposing AWS resources to the world.\n\n"
  },
  {
    "path": "examples/service_validation/admission_controller.yaml",
    "content": "apiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: opa-sa\n---\nkind: ClusterRoleBinding\napiVersion: rbac.authorization.k8s.io/v1\nmetadata:\n  name: opa-rolebinding\nsubjects:\n- kind: ServiceAccount\n  name: opa-sa\n  namespace: opa\nroleRef:\n  kind: ClusterRole\n  name: cluster-admin\n  apiGroup: rbac.authorization.k8s.io\n---\nkind: Service\napiVersion: v1\nmetadata:\n  name: opa\nspec:\n  selector:\n    app: opa\n  ports:\n  - name: https\n    protocol: TCP\n    port: 443\n    targetPort: 443\n---\napiVersion: extensions/v1beta1\nkind: Deployment\nmetadata:\n  labels:\n    app: opa\n  name: opa\nspec:\n  replicas: 1\n  template:\n    metadata:\n      labels:\n        app: opa\n      name: opa\n    spec:\n      serviceAccountName: opa-sa\n      containers:\n        - name: opa\n          image: openpolicyagent/opa:0.8.0\n          args:\n            - \"run\"\n            - \"--server\"\n            - \"--tls-cert-file=/certs/tls.crt\"\n            - \"--tls-private-key-file=/certs/tls.key\"\n            - \"--addr=0.0.0.0:443\"\n            - \"--insecure-addr=127.0.0.1:8181\"\n          volumeMounts:\n            - readOnly: true\n              mountPath: /certs\n              name: opa-server\n        - name: kube-mgmt\n          image: openpolicyagent/kube-mgmt:0.12.1\n      volumes:\n        - name: opa-server\n          secret:\n            secretName: opa-server\n---\nkind: ValidatingWebhookConfiguration\napiVersion: admissionregistration.k8s.io/v1\nmetadata:\n  name: opa-validating-webhook\nwebhooks:\n  - name: validating-webhook.openpolicyagent.org\n    admissionReviewVersions: [\"v1beta1\"]\n    rules:\n      - operations: [\"CREATE\"]\n        apiGroups: [\"*\"]\n        apiVersions: [\"v1\"]\n        resources: [\"services\"]\n    clientConfig:\n      caBundle: REPLACE_WITH_SECRET\n      service:\n        namespace: opa\n        name: opa\n    sideEffects: None\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: service-check\ndata:\n  main: |\n    package kubernetes.admission\n\n    import data.kubernetes.namespaces\n\n    deny[msg] {\n        input.request.kind.kind = \"Service\"\n        input.request.operation = \"CREATE\"\n        servicetype = input.request.object.spec.type\n        contains(servicetype, \"LoadBalancer\")\n        not input.request.object.spec.loadBalancerSourceRanges\n        msg = sprintf(\"Rejecting service of type %q without specifying spec.loadBalancerSourceRanges\", [servicetype])\n    }\n---\nkind: ConfigMap\napiVersion: v1\nmetadata:\n  name: opa-default-system-main\ndata:\n  main: |\n    package system\n\n    import data.kubernetes.admission\n\n    main = {\n      \"apiVersion\": \"admission.k8s.io/v1beta1\",\n      \"kind\": \"AdmissionReview\",\n      \"response\": response,\n    }\n\n    default response = {\"allowed\": true}\n\n    response = {\n        \"allowed\": false,\n        \"status\": {\n            \"reason\": reason,\n        },\n    } {\n        reason = concat(\", \", admission.deny)\n        reason != \"\"\n    }\n"
  },
  {
    "path": "examples/service_validation/install.sh",
    "content": "#!/bin/bash\n\nset -ex\n\nOUT_DIR=/tmp/opa\nrm -rf ${OUT_DIR}; mkdir -p ${OUT_DIR}\n\nopenssl genrsa -out ${OUT_DIR}/ca.key 2048\nopenssl req -x509 -new -nodes -key ${OUT_DIR}/ca.key -days 100000 -out ${OUT_DIR}/ca.crt -subj \"/CN=admission_ca\"\ncat >${OUT_DIR}/server.conf <<EOF\n[req]\nreq_extensions = v3_req\ndistinguished_name = req_distinguished_name\n[req_distinguished_name]\n[ v3_req ]\nbasicConstraints = CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment\nextendedKeyUsage = clientAuth, serverAuth\nEOF\nopenssl genrsa -out ${OUT_DIR}/server.key 2048\nopenssl req -new -key ${OUT_DIR}/server.key -out ${OUT_DIR}/server.csr -subj \"/CN=opa.opa.svc\" -config ${OUT_DIR}/server.conf\nopenssl x509 -req -in ${OUT_DIR}/server.csr -CA ${OUT_DIR}/ca.crt -CAkey ${OUT_DIR}/ca.key -CAcreateserial -out ${OUT_DIR}/server.crt -days 100000 -extensions v3_req -extfile ${OUT_DIR}/server.conf\n\ninstall_namespace=opa\ncaBundle=$(base64 ${OUT_DIR}/ca.crt)\ncp admission_controller.yaml ${OUT_DIR}/admission_controller.yaml\ngsed -i \"s/REPLACE_WITH_SECRET/${caBundle}/\" ${OUT_DIR}/admission_controller.yaml\n\nkubectl create namespace ${install_namespace}\nkubectl create secret tls opa-server --cert=${OUT_DIR}/server.crt --key=${OUT_DIR}/server.key --namespace ${install_namespace}\nkubectl apply -f ${OUT_DIR}/admission_controller.yaml --namespace ${install_namespace}\n\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/open-policy-agent/kube-mgmt\n\ngo 1.24.11\n\nrequire (\n\tgithub.com/open-policy-agent/opa v1.5.1\n\tgithub.com/sirupsen/logrus v1.9.3\n\tgithub.com/spf13/cobra v1.9.1\n\tk8s.io/api v0.32.3\n\tk8s.io/apimachinery v0.32.3\n\tk8s.io/client-go v0.32.3\n)\n\nrequire (\n\tgithub.com/agnivade/levenshtein v1.2.1 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/bytecodealliance/wasmtime-go/v3 v3.0.2 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/containerd/containerd/v2 v2.1.1 // indirect\n\tgithub.com/containerd/errdefs v1.0.0 // indirect\n\tgithub.com/containerd/log v0.1.0 // indirect\n\tgithub.com/containerd/platforms v1.0.0-rc.1 // indirect\n\tgithub.com/emicklei/go-restful/v3 v3.11.0 // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/fsnotify/fsnotify v1.9.0 // indirect\n\tgithub.com/fxamacker/cbor/v2 v2.7.0 // indirect\n\tgithub.com/go-ini/ini v1.67.0 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.21.0 // indirect\n\tgithub.com/go-openapi/jsonreference v0.20.2 // indirect\n\tgithub.com/go-openapi/swag v0.23.0 // indirect\n\tgithub.com/gobwas/glob v0.2.3 // indirect\n\tgithub.com/google/gnostic-models v0.6.8 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/gorilla/mux v1.8.1 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/klauspost/compress v1.18.0 // indirect\n\tgithub.com/mailru/easyjson v0.7.7 // indirect\n\tgithub.com/moby/locker v1.0.1 // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/opencontainers/go-digest v1.0.0 // indirect\n\tgithub.com/opencontainers/image-spec v1.1.1 // indirect\n\tgithub.com/prometheus/client_golang v1.22.0 // indirect\n\tgithub.com/prometheus/client_model v0.6.2 // indirect\n\tgithub.com/prometheus/common v0.62.0 // indirect\n\tgithub.com/prometheus/procfs v0.15.1 // indirect\n\tgithub.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect\n\tgithub.com/tchap/go-patricia/v2 v2.3.2 // indirect\n\tgithub.com/vektah/gqlparser/v2 v2.5.26 // indirect\n\tgithub.com/x448/float16 v0.8.4 // indirect\n\tgithub.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect\n\tgithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect\n\tgithub.com/yashtewari/glob-intersection v0.2.0 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.1.0 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect\n\tgo.opentelemetry.io/otel v1.35.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.35.0 // indirect\n\tgo.opentelemetry.io/otel/sdk v1.35.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.35.0 // indirect\n\tgolang.org/x/sync v0.14.0 // indirect\n\tgopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect\n\toras.land/oras-go/v2 v2.5.0 // indirect\n)\n\nrequire (\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/go-logr/logr v1.4.2 // indirect\n\tgithub.com/gogo/protobuf v1.3.2 // indirect\n\tgithub.com/golang/protobuf v1.5.4 // indirect; indire4ct\n\tgithub.com/google/go-cmp v0.7.0 // indirect\n\tgithub.com/google/gofuzz v1.2.0 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // 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/pkg/errors v0.9.1 // indirect\n\tgithub.com/spf13/pflag v1.0.6 // indirect\n\tgolang.org/x/net v0.39.0 // indirect\n\tgolang.org/x/oauth2 v0.27.0 // indirect\n\tgolang.org/x/sys v0.33.0 // indirect\n\tgolang.org/x/term v0.31.0 // indirect\n\tgolang.org/x/text v0.24.0 // indirect\n\tgolang.org/x/time v0.11.0 // indirect\n\tgoogle.golang.org/protobuf v1.36.6 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tk8s.io/klog/v2 v2.130.1 // indirect\n\tk8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect\n\tk8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect\n\tsigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect\n\tsigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect\n\tsigs.k8s.io/yaml v1.4.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=\ngithub.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=\ngithub.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM=\ngithub.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU=\ngithub.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=\ngithub.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=\ngithub.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=\ngithub.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=\ngithub.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=\ngithub.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=\ngithub.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA=\ngithub.com/bytecodealliance/wasmtime-go/v3 v3.0.2/go.mod h1:RnUjnIXxEJcL6BgCvNyzCCRzZcxCgsZCi+RNlvYor5Q=\ngithub.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=\ngithub.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/containerd/containerd/v2 v2.1.1 h1:znnkm7Ajz8lg8BcIPMhc/9yjBRN3B+OkNKqKisKfwwM=\ngithub.com/containerd/containerd/v2 v2.1.1/go.mod h1:zIfkQj4RIodclYQkX7GSSswSwgP8d/XxDOtOAoSDIGU=\ngithub.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=\ngithub.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=\ngithub.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=\ngithub.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=\ngithub.com/containerd/platforms v1.0.0-rc.1 h1:83KIq4yy1erSRgOVHNk1HYdPvzdJ5CnsWaRoJX4C41E=\ngithub.com/containerd/platforms v1.0.0-rc.1/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dgraph-io/badger/v4 v4.7.0 h1:Q+J8HApYAY7UMpL8d9owqiB+odzEc0zn/aqOD9jhc6Y=\ngithub.com/dgraph-io/badger/v4 v4.7.0/go.mod h1:He7TzG3YBy3j4f5baj5B7Zl2XyfNe5bl4Udl0aPemVA=\ngithub.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM=\ngithub.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI=\ngithub.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo=\ngithub.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=\ngithub.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=\ngithub.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=\ngithub.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=\ngithub.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=\ngithub.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=\ngithub.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=\ngithub.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=\ngithub.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI=\ngithub.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk=\ngithub.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=\ngithub.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=\ngithub.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=\ngithub.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=\ngithub.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=\ngithub.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=\ngithub.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=\ngithub.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=\ngithub.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=\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/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=\ngithub.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=\ngithub.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=\ngithub.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=\ngithub.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=\ngithub.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=\ngithub.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q=\ngithub.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=\ngithub.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=\ngithub.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=\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/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/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=\ngithub.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=\ngithub.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\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/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=\ngithub.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\ngithub.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=\ngithub.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=\ngithub.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=\ngithub.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=\ngithub.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=\ngithub.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=\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.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=\ngithub.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=\ngithub.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=\ngithub.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=\ngithub.com/open-policy-agent/opa v1.5.1 h1:LTxxBJusMVjfs67W4FoRcnMfXADIGFMzpqnfk6D08Cg=\ngithub.com/open-policy-agent/opa v1.5.1/go.mod h1:bYbS7u+uhTI+cxHQIpzvr5hxX0hV7urWtY+38ZtjMgk=\ngithub.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=\ngithub.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=\ngithub.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=\ngithub.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=\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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q=\ngithub.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=\ngithub.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=\ngithub.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=\ngithub.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=\ngithub.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=\ngithub.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=\ngithub.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=\ngithub.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ=\ngithub.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=\ngithub.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=\ngithub.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=\ngithub.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=\ngithub.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=\ngithub.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=\ngithub.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=\ngithub.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=\ngithub.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\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/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=\ngithub.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/tchap/go-patricia/v2 v2.3.2 h1:xTHFutuitO2zqKAQ5rCROYgUb7Or/+IC3fts9/Yc7nM=\ngithub.com/tchap/go-patricia/v2 v2.3.2/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=\ngithub.com/vektah/gqlparser/v2 v2.5.26 h1:REqqFkO8+SOEgZHR/eHScjjVjGS8Nk3RMO/juiTobN4=\ngithub.com/vektah/gqlparser/v2 v2.5.26/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo=\ngithub.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=\ngithub.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=\ngithub.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=\ngithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=\ngithub.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=\ngithub.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBeEWqThExu54RFg=\ngithub.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok=\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/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=\ngo.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=\ngo.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=\ngo.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk=\ngo.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=\ngo.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=\ngo.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=\ngo.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=\ngo.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=\ngo.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=\ngo.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=\ngo.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=\ngo.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=\ngo.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\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/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=\ngolang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=\ngolang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=\ngolang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=\ngolang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=\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/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=\ngolang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=\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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=\ngolang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=\ngolang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=\ngolang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=\ngolang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=\ngolang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=\ngolang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\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/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=\ngolang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=\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/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ=\ngoogle.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM=\ngoogle.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=\ngoogle.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=\ngoogle.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=\ngopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=\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.v3 v3.0.0-20200313102051-9f266ea9e77c/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=\nk8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls=\nk8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k=\nk8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U=\nk8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=\nk8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU=\nk8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY=\nk8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=\nk8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=\nk8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y=\nk8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=\nk8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=\nk8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=\noras.land/oras-go/v2 v2.5.0 h1:o8Me9kLY74Vp5uw07QXPiitjsw7qNXi8Twd+19Zf02c=\noras.land/oras-go/v2 v2.5.0/go.mod h1:z4eisnLP530vwIOUOJeBIj0aGI0L1C3d53atvCBqZHg=\nsigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=\nsigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=\nsigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA=\nsigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=\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": "internal/expect/client.go",
    "content": "package expect\n\nimport (\n\t\"encoding/json\"\n\t\"errors\"\n\n\topa_client \"github.com/open-policy-agent/kube-mgmt/pkg/opa\"\n)\n\n// Client emulates OPA Client API\ntype Client struct {\n\tPrefixList []string\n\t// This function will be called on every request\n\tactor func(req Request, value interface{}) error\n}\n\n// Prefix implements Data\nfunc (f *Client) Prefix(path string) opa_client.Data {\n\tf.PrefixList = append(f.PrefixList, path)\n\treturn f\n}\n\n// PatchData implements Data.\nfunc (f *Client) PatchData(path string, op string, value *interface{}) (err error) {\n\treq := Request{\n\t\treq:  patchRequest,\n\t\tpath: path,\n\t\top:   op,\n\t}\n\tvar actualValue interface{}\n\tif value != nil {\n\t\tactualValue = *value\n\t}\n\treturn f.actor(req, actualValue)\n}\n\n// PutData implements Data\nfunc (f *Client) PutData(path string, value interface{}) (err error) {\n\treq := Request{\n\t\treq:  putRequest,\n\t\tpath: path,\n\t}\n\treturn f.actor(req, value)\n}\n\nvar errNotSupported = errors.New(\"PostData not supported\")\n\n// PostData implements Data. Currently not supported.\nfunc (*Client) PostData(string, interface{}) (json.RawMessage, error) {\n\treturn nil, errNotSupported\n}\n\n// InsertPolicy implements Policies\nfunc (f *Client) InsertPolicy(path string, value []byte) (err error) {\n\treq := Request{\n\t\treq:  insertPolicyRequest,\n\t\tpath: path,\n\t}\n\treturn f.actor(req, value)\n}\n\n// DeletePolicy implements Policies\nfunc (f *Client) DeletePolicy(path string) (err error) {\n\treq := Request{\n\t\treq:  deletePolicyRequest,\n\t\tpath: path,\n\t}\n\treturn f.actor(req, nil)\n}\n"
  },
  {
    "path": "internal/expect/request.go",
    "content": "package expect\n\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\t\"time\"\n)\n\ntype request string\n\nconst (\n\tpatchRequest        request = \"PatchData\"\n\tputRequest          request = \"PutData\"\n\tinsertPolicyRequest request = \"InsertPolicy\"\n\tdeletePolicyRequest request = \"DeletePolicy\"\n\tnoRequest           request = \"Nothing\"\n)\n\n// Request represents an operation against the mock client.\ntype Request struct {\n\treq      request\n\tpath     string\n\top       string\n\tvalue    []byte\n\tinterval time.Duration // Only applies to script.Expect(Nothing())\n}\n\n// Equals compares two requests\nfunc (expected Request) Equals(actual Request) bool {\n\treturn expected.req == actual.req &&\n\t\texpected.path == actual.path &&\n\t\t(expected.req != patchRequest || expected.op == actual.op) &&\n\t\t(expected.value == nil || reflect.DeepEqual(expected.value, actual.value))\n}\n\n// String implements fmt.Stringer\nfunc (r Request) String() string {\n\tif r.value != nil {\n\t\treturn fmt.Sprintf(\"{req: %q, path: %q, op: %q, value: %q}\", r.req, r.path, r.op, string(r.value))\n\t}\n\treturn fmt.Sprintf(\"{req: %q, path: %q, op: %q}\", r.req, r.path, r.op)\n}\n\nfunc optional(expected ...[]byte) []byte {\n\tif len(expected) > 0 {\n\t\treturn expected[0]\n\t}\n\treturn nil\n}\n\n// PutData describes a PutData request with an optional expected value\n// (expected value can be omitted)\nfunc PutData(path string, expected ...[]byte) Request {\n\treturn Request{\n\t\treq:   putRequest,\n\t\tpath:  path,\n\t\tvalue: optional(expected...),\n\t}\n}\n\n// PatchData describes a PatchData request with an optional expected value\n// (expected value can be omitted)\nfunc PatchData(path string, op string, expected ...[]byte) Request {\n\treturn Request{\n\t\treq:   patchRequest,\n\t\tpath:  path,\n\t\top:    op,\n\t\tvalue: optional(expected...),\n\t}\n}\n\n// InsertPolicy describes a InsertPolicy request with an optional expected value\n// (expected value can be omitted)\nfunc InsertPolicy(path string, expected ...[]byte) Request {\n\treturn Request{\n\t\treq:   insertPolicyRequest,\n\t\tpath:  path,\n\t\tvalue: optional(expected...),\n\t}\n}\n\n// DeletePolicy describes a DeletePolicy request with an optional expected value\n// (expected value can be omitted)\nfunc DeletePolicy(path string) Request {\n\treturn Request{\n\t\treq:  deletePolicyRequest,\n\t\tpath: path,\n\t}\n}\n\n// Nothing describes an empty action. The client must not get any request for the given time\nfunc Nothing(duration time.Duration) Request {\n\treturn Request{\n\t\treq:      noRequest,\n\t\tinterval: duration,\n\t}\n}\n\n// Action performed when an expected Request arrives.\n// The request will return the result of invoking the Action.\ntype Action func() error\n\n// Step combines a Request and an Action\ntype Step struct {\n\tRequest\n\tAction\n}\n\n// Do turns a Request into a Step\nfunc (req Request) Do(action Action) Step {\n\treturn Step{\n\t\tRequest: req,\n\t\tAction:  action,\n\t}\n}\n\n// DoError is a shortcut for Do(func() error { return err })\nfunc (req Request) DoError(err error) Step {\n\treturn Step{\n\t\tRequest: req,\n\t\tAction: func() error {\n\t\t\treturn err\n\t\t},\n\t}\n}\n\n// End is a shortcut for Do(nil)\nfunc (req Request) End() Step {\n\treturn Step{\n\t\tRequest: req,\n\t}\n}\n"
  },
  {
    "path": "internal/expect/script.go",
    "content": "package expect\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"reflect\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/client-go/tools/cache\"\n)\n\n// Script is a sequence of expected Requests for a Client,\n// and the Actions to perform on each Request.\ntype Script []Step\n\n// String implements fmt.Stringer\nfunc (s Script) String() string {\n\treturn s.strings(\"\\n\")\n}\n\n// Play creates a client with the script provided, and runs the show.\n// When the script ends, the show is cancelled and the final state\n// of the client returned.\nfunc Play(t *testing.T, script Script, show func(ctx context.Context, client *Client)) *Client {\n\tsteps := len(script)\n\tif steps <= 0 || show == nil {\n\t\treturn nil\n\t}\n\n\tctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(10*time.Second))\n\t// Arrange to cancel the context on the last step\n\tlast := script[steps-1]\n\tscript[steps-1].Action = func() error {\n\t\tdefer cancel()\n\t\tif last.Action == nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn last.Action()\n\t}\n\n\tvar (\n\t\tactor     func(req Request, value interface{}) error\n\t\timprovise func(cursor int)\n\t\tcursor    int = 0\n\t)\n\n\tactor = func(req Request, value interface{}) error {\n\t\tif cursor >= len(script) {\n\t\t\tt.Fatalf(\"Expected at most %d steps, got one more request %v\", len(script), req)\n\t\t}\n\t\t// Save the actual value received to req. We do it\n\t\t// here because we have the *testing.T instance, and\n\t\t// can call t.Fatal if conversions fail.\n\t\tif req.req == insertPolicyRequest {\n\t\t\treq.value = value.([]byte)\n\t\t} else {\n\t\t\treq.value = MustRoundTrip(t, value)\n\t\t}\n\t\t// Check that the request matches the cue\n\t\tcue := script[cursor]\n\t\tif !cue.Equals(req) {\n\t\t\tseq := script[:cursor+1].strings(\"\\n\\t\")\n\t\t\tt.Fatalf(\"Expected sequence:\\n\\t%v\\nError at step %d, got:\\n\\t%v\", seq, cursor, req)\n\t\t}\n\t\tcursor++\n\t\tif cursor < len(script) && script[cursor].req == noRequest {\n\t\t\t// If the next update is timed, schedule it.\n\t\t\tgo improvise(cursor)\n\t\t}\n\t\tif cue.Action == nil {\n\t\t\treturn nil\n\t\t}\n\t\treturn cue.Action()\n\t}\n\n\t// improvise triggers the step without any external input\n\timprovise = func(cursor int) {\n\t\t<-time.After(script[cursor].interval)\n\t\tactor(script[cursor].Request, nil)\n\t}\n\n\tclient := &Client{actor: actor}\n\tif script[0].req == noRequest {\n\t\t// boot the script if the first step is a wait.\n\t\tgo improvise(0)\n\t}\n\n\tshow(ctx, client)\n\tif deadline, ok := ctx.Deadline(); ok && deadline.Before(time.Now()) {\n\t\tt.Fatalf(\"Test %s failed because of timeout\", t.Name())\n\t}\n\treturn client\n}\n\n// MustMarshal marshals the objet to JSON, calls t.Fatal on error\nfunc MustMarshal(t *testing.T, obj interface{}) []byte {\n\tt.Helper()\n\tdata, err := json.Marshal(obj)\n\tif err != nil {\n\t\tt.Fatalf(\"error marshalling JSON: %s\", err)\n\t}\n\treturn data\n}\n\n// MustUmnarshal unmarshals the objet from JSON, calls t.Fatal on error\nfunc MustUnmarshal(t *testing.T, data []byte) interface{} {\n\tt.Helper()\n\n\tvar result interface{}\n\tif len(data) > 0 {\n\t\terr := json.Unmarshal(data, &result)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"error unmarshalling JSON: %s\", err)\n\t\t}\n\t}\n\treturn result\n}\n\n// mustRoundtrip marshals the object to JSON consistently.\n//\n// Kubernetes objects have custom marshallers that output the\n// json in a custom order. So comparing the marshalled representation of\n// a kubernetes object with that of a map or an *unstructured.Unstructured\n// built from that same object will fail.\n//\n// MustRoundTrip will make sure the generated string is comparable.\nfunc MustRoundTrip(t *testing.T, obj interface{}) []byte {\n\treturn MustMarshal(t, MustUnmarshal(t, MustMarshal(t, obj)))\n}\n\n// MustEqual compares the values and calls t.Fatal on error\nfunc MustEqual(t *testing.T, result, expected interface{}) {\n\tt.Helper()\n\n\tif !reflect.DeepEqual(result, expected) {\n\t\tt.Fatalf(\"Expected:\\n\\n%q\\n\\nActual:\\n\\n%q\\n\", expected, result)\n\t}\n}\n\n// MustKey gets the mentaNamespaceKey of an object\nfunc MustKey(t *testing.T, obj runtime.Object) string {\n\tt.Helper()\n\n\tpath, err := cache.MetaNamespaceKeyFunc(obj)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to get path from object %v: %v\", obj, err)\n\t}\n\treturn path\n}\n\n// strings formats the Script as a list of strings for printing\nfunc (s Script) strings(sep string) string {\n\tsteps := make([]string, 0, len(s))\n\tfor cursor, step := range s {\n\t\tsteps = append(steps, fmt.Sprintf(\"%d: %s\", cursor, step.String()))\n\t}\n\treturn strings.Join(steps, sep)\n}\n"
  },
  {
    "path": "justfile",
    "content": "K3D := \"kube-mgmt\"\nTEST_RESULTS := 'build/test-results'\n\n@_default:\n  @just --list\n\n# golang linter\n[group('code quality')]\nlint-go:\n  go vet ./...\n  staticcheck ./...\n\n# helm linter\n[group('code quality')]\nlint-helm filter=\"*\":\n  #!/usr/bin/env -S bash -euo pipefail\n\n  mkdir -p {{TEST_RESULTS}}/helm-unittest\n\n  helm unittest -f '../../test/lint/{{filter}}.yaml' \\\n    --output-file {{TEST_RESULTS}}/helm-unittest/lint.xml --output-type JUnit charts/opa-kube-mgmt\n\n# run all linters\n[group('code quality')]\nlint: lint-go lint-helm\n\n# run helm unit tests\n[group('code quality')]\ntest-helm filter=\"*\":\n  #!/usr/bin/env -S bash -euo pipefail\n\n  mkdir -p {{TEST_RESULTS}}/helm-unittest\n\n  helm unittest -f '../../test/unit/{{filter}}.yaml' \\\n    --output-file {{TEST_RESULTS}}/helm-unittest/unit.xml --output-type JUnit charts/opa-kube-mgmt\n\n# run golang unit tests\n[group('code quality')]\ntest-go:\n  go test ./...\n\n# run linters and unit tests\n[group('code quality')]\ntest: lint test-go test-helm\n\n@_token:\n  kubectl exec deploy/opa-kube-mgmt -n default -c mgmt -- cat /bootstrap/mgmt-token\n\n# run e2e test using chainsaw and hurl\n[group('code quality')]\ntest-e2e E2E_TEST=\"\": _ctx\n  #!/usr/bin/env -S bash -euo pipefail\n\n  SCENARIO=\"{{E2E_TEST}}\"\n  if [ -z \"$SCENARIO\" ]; then\n    SCENARIO=$(find test/e2e/ -mindepth 1 -maxdepth 1 -type d | sort | fzf --header \"Select e2e scenario\")\n  fi\n\n  devspace purge\n  devspace deploy --var E2E_TEST=\"$SCENARIO\"\n\n  mkdir -p {{TEST_RESULTS}}/chainsaw\n\n  OPA_TOKEN=$(just _token 2>/dev/null || true) chainsaw test \"$SCENARIO\" --quiet --namespace default \\\n    --report-format JUNIT-TEST \\\n    --report-name \"$(basename \"$SCENARIO\")\" --report-path {{TEST_RESULTS}}/chainsaw\n\n# run all e2e tests\n[group('code quality')]\ntest-e2e-all:\n  #!/usr/bin/env -S bash -euo pipefail\n\n  for E in $(find test/e2e/ -name 'chainsaw-test.yaml'|xargs -n1 dirname|sort); do\n    just test-e2e \"${E}\"\n  done\n\n# start kube-mgmt in local k8s cluster\n[group('deployment')]\n@up: _ctx\n  devspace deploy --var E2E_TEST=test/e2e/default\n\n# stop kube-mgmt in local k8s cluster\n[group('deployment')]\n@down: _ctx\n  devspace purge --force-purge && rm -rf .devspace/\n\n@_ctx:\n  kubectl config use-context k3d-{{K3D}}\n\n_bundle:\n  #!/usr/bin/env -S bash -euo pipefail\n\n  opa build -b ./test/e2e/replicate_auto/bundle -o ./test/e2e/replicate_auto/bundle.tar.gz\n  kubectl delete configmap -n default bundle --ignore-not-found\n  kubectl create configmap -n default bundle --from-file ./test/e2e/replicate_auto/bundle.tar.gz\n\n# delete local k8s cluster\n[group('deployment')]\n@k3d-down:\n  k3d cluster delete {{K3D}} || true\n\n# (re) create local k8s cluster using k3d\n[group('deployment')]\nall: k3d-down && _ctx _bundle\n  #!/usr/bin/env -S bash -euo pipefail\n\n  echo '\n  apiVersion: k3d.io/v1alpha5\n  kind: Simple\n  metadata:\n    name: {{K3D}}\n  servers: 1\n  agents: 0\n  image: rancher/k3s:v1.33.9-k3s1\n  registries:\n    create:\n      name: k3d-{{K3D}}-registry\n      host: \"0.0.0.0\"\n      hostPort: \"5001\"\n    config: |\n      mirrors:\n        \"localhost:5001\":\n          endpoint:\n            - http://k3d-{{K3D}}-registry:5000\n  ports:\n    - port: 8080:80\n      nodeFilters: [\"loadbalancer\"]\n    - port: 8443:443\n      nodeFilters: [\"loadbalancer\"]\n  options:\n    k3s:\n      extraArgs:\n        - arg: \"--disable=local-storage,metrics-server\"\n          nodeFilters: [\"server:*\"]\n  ' | k3d cluster create --config /dev/stdin\n\n  kubectl config set-context k3d-{{K3D}} --namespace default\n\n  docker login -u {{K3D}} -p {{K3D}} localhost:5001\n\n  kubectl wait --for=create crd/ingressroutetcps.traefik.io --timeout=2m\n  sleep 3\n  kubectl wait --for=condition=Established crd/ingressroutetcps.traefik.io --timeout=30s\n\n"
  },
  {
    "path": "pkg/configmap/configmap.go",
    "content": "// Copyright 2017 The OPA Authors.  All rights reserved.\n// Use of this source code is governed by an Apache2\n// license that can be found in the LICENSE file.\n\npackage configmap\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"hash/fnv\"\n\t\"sort\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/open-policy-agent/kube-mgmt/pkg/opa\"\n\t\"github.com/sirupsen/logrus\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/fields\"\n\t\"k8s.io/apimachinery/pkg/labels\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/apimachinery/pkg/runtime/serializer\"\n\t\"k8s.io/apimachinery/pkg/selection\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/tools/cache\"\n)\n\nconst (\n\tdefaultRetries       = 2\n\tstatusAnnotationKey  = \"openpolicyagent.org/kube-mgmt-status\"\n\tretriesAnnotationKey = \"openpolicyagent.org/kube-mgmt-retries\"\n\t// Special namespace in Kubernetes federation that holds scheduling policies.\n\t// commented because staticcheck: 'const kubeFederationSchedulingPolicy is unused (U1000)'\n\t// kubeFederationSchedulingPolicy = \"kube-federation-scheduling-policy\"\n\tresyncPeriod        = time.Second * 60\n\tsyncResetBackoffMin = time.Second\n\tsyncResetBackoffMax = time.Second * 30\n)\n\n// Label validator\nfunc CustomLabel(key, value string) error {\n\t_, err := labels.NewRequirement(key, selection.Equals, []string{value})\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// DefaultConfigMapMatcher returns a function that will match configmaps in\n// specified namespaces and/or with a policy or data label. The first bool return\n// value specifies a policy/data match and the second bool indicates if the configmap\n// contains a policy.\nfunc DefaultConfigMapMatcher(namespaces []string, enablePolicies, enableData bool, policyLabelKey, policyLabelValue, dataLabelKey, dataLabelValue string) func(*v1.ConfigMap) (bool, bool) {\n\treturn func(cm *v1.ConfigMap) (bool, bool) {\n\t\tvar match, isPolicy bool\n\n\t\tif enableData {\n\t\t\tmatch = matchesNamespace(cm, namespaces) && matchesLabel(cm, dataLabelKey, dataLabelValue)\n\t\t}\n\n\t\tif !match && enablePolicies {\n\t\t\tmatch = matchesNamespace(cm, namespaces) && matchesLabel(cm, policyLabelKey, policyLabelValue)\n\n\t\t\tif match {\n\t\t\t\tisPolicy = true\n\t\t\t}\n\t\t}\n\t\treturn match, isPolicy\n\t}\n}\n\nfunc matchesLabel(cm *v1.ConfigMap, labelKey, labelValue string) bool {\n\treturn cm.Labels[labelKey] == labelValue\n}\n\nfunc matchesNamespace(cm *v1.ConfigMap, namespaces []string) bool {\n\tfor _, ns := range namespaces {\n\t\tif ns == cm.Namespace || ns == \"*\" {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// Sync replicates policies or data stored in the API server as ConfigMaps into OPA.\ntype Sync struct {\n\tkubeconfig *rest.Config\n\topa        opa.Client\n\tclientset  *kubernetes.Clientset\n\tmatcher    func(*v1.ConfigMap) (bool, bool)\n}\n\n// New returns a new Sync that can be started.\nfunc New(kubeconfig *rest.Config, opa opa.Client, matcher func(*v1.ConfigMap) (bool, bool)) *Sync {\n\tcpy := *kubeconfig\n\tcpy.GroupVersion = &schema.GroupVersion{\n\t\tVersion: \"v1\",\n\t}\n\tcpy.APIPath = \"/api\"\n\tcpy.ContentType = runtime.ContentTypeJSON\n\tscheme := runtime.NewScheme()\n\tcpy.NegotiatedSerializer = serializer.WithoutConversionCodecFactory{CodecFactory: serializer.NewCodecFactory(scheme)}\n\tbuilder := runtime.NewSchemeBuilder(func(scheme *runtime.Scheme) error {\n\t\tscheme.AddKnownTypes(\n\t\t\t*cpy.GroupVersion,\n\t\t\t&metav1.ListOptions{},\n\t\t\t&metav1.Status{},\n\t\t\t&v1.ConfigMapList{},\n\t\t\t&v1.ConfigMap{})\n\t\treturn nil\n\t})\n\tbuilder.AddToScheme(scheme)\n\treturn &Sync{\n\t\tkubeconfig: &cpy,\n\t\topa:        opa,\n\t\tmatcher:    matcher,\n\t}\n}\n\n// Run starts the synchronizer. To stop the synchronizer send a message to the\n// channel.\nfunc (s *Sync) Run(namespaces []string) (chan struct{}, error) {\n\tclient, err := rest.RESTClientFor(s.kubeconfig)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ts.clientset, err = kubernetes.NewForConfig(s.kubeconfig)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tquit := make(chan struct{})\n\n\tlogrus.Infof(\"Policy/data ConfigMap processor connected to K8s: namespaces=%v\", namespaces)\n\tfor _, namespace := range namespaces {\n\t\tif namespace == \"*\" {\n\t\t\tnamespace = v1.NamespaceAll\n\t\t}\n\t\tlisterWatcher := cache.NewListWatchFromClient(\n\t\t\tclient,\n\t\t\t\"configmaps\",\n\t\t\tnamespace,\n\t\t\tfields.Everything())\n\t\t_, controller := cache.NewInformerWithOptions(cache.InformerOptions{\n\t\t\tListerWatcher: listerWatcher,\n\t\t\tObjectType:    &v1.ConfigMap{},\n\t\t\tHandler: cache.ResourceEventHandlerFuncs{\n\t\t\t\tAddFunc:    s.add,\n\t\t\t\tUpdateFunc: s.update,\n\t\t\t\tDeleteFunc: s.delete,\n\t\t\t},\n\t\t\tResyncPeriod: 0, // Set to 0 as in the original code\n\t\t})\n\t\tgo controller.Run(quit)\n\t}\n\treturn quit, nil\n}\n\nfunc (s *Sync) add(obj interface{}) {\n\tcm := obj.(*v1.ConfigMap)\n\tif match, isPolicy := s.matcher(cm); match {\n\t\tlogrus.Debugf(\"OnAdd cm=%v/%v, isPolicy=%v\", cm.Namespace, cm.Name, isPolicy)\n\t\ts.syncAdd(cm, isPolicy)\n\t}\n}\n\nfunc (s *Sync) update(oldObj, obj interface{}) {\n\toldCm, cm := oldObj.(*v1.ConfigMap), obj.(*v1.ConfigMap)\n\tif match, isPolicy := s.matcher(cm); match {\n\t\tlogrus.Debugf(\"OnUpdate cm=%v/%v, isPolicy=%v, oldVer=%v, newVer=%v\",\n\t\t\tcm.Namespace, cm.Name, isPolicy, oldCm.GetResourceVersion(), cm.GetResourceVersion())\n\t\tif cm.GetResourceVersion() != oldCm.GetResourceVersion() {\n\t\t\tnewFp, oldFp := fingerprint(cm), fingerprint(oldCm)\n\t\t\trtrVal := cm.Annotations[retriesAnnotationKey]\n\t\t\tlogrus.Debugf(\"OnUpdate cm=%v/%v, retries=%v, oldFp=%v, newFp=%v\", cm.Namespace, cm.Name, rtrVal, oldFp, newFp)\n\t\t\tif newFp != oldFp || rtrVal != \"0\" {\n\t\t\t\ts.syncAdd(cm, isPolicy)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// check if the label was removed\n\t\tif match, isPolicy := s.matcher(oldCm); match {\n\t\t\ts.syncRemove(oldCm, isPolicy)\n\t\t}\n\t}\n}\n\nfunc (s *Sync) delete(obj interface{}) {\n\tif d, ok := obj.(cache.DeletedFinalStateUnknown); ok {\n\t\tobj = d.Obj\n\t}\n\tcm := obj.(*v1.ConfigMap)\n\tif match, isPolicy := s.matcher(cm); match {\n\t\tlogrus.Debugf(\"OnDelete cm=%v/%v\", cm.Namespace, cm.Name)\n\t\ts.syncRemove(cm, isPolicy)\n\t}\n}\n\nfunc (s *Sync) syncAdd(cm *v1.ConfigMap, isPolicy bool) {\n\tpath := fmt.Sprintf(\"%v/%v\", cm.Namespace, cm.Name)\n\tlogrus.Debugf(\"Adding cm=%v, isPolicy=%v\", path, isPolicy)\n\t// sort keys so that errors, if any, are always in the same order\n\tsortedKeys := make([]string, 0, len(cm.Data))\n\tfor key := range cm.Data {\n\t\tsortedKeys = append(sortedKeys, key)\n\t}\n\tsort.Strings(sortedKeys)\n\tvar syncErr errList\n\tfor _, key := range sortedKeys {\n\t\tvalue := cm.Data[key]\n\t\tid := fmt.Sprintf(\"%v/%v\", path, key)\n\t\tvar err error\n\t\tif isPolicy {\n\t\t\terr = s.opa.InsertPolicy(id, []byte(value))\n\t\t\tlogrus.Infof(\"Added policy %v, err=%v\", id, err)\n\t\t} else {\n\t\t\t// We don't need to know the JSON structure, just pass it\n\t\t\t// directly to the OPA data store.\n\t\t\tvar data map[string]interface{}\n\t\t\tif err = json.Unmarshal([]byte(value), &data); err != nil {\n\t\t\t\tlogrus.Errorf(\"Failed to parse JSON data in configmap with id=%s\", id)\n\t\t\t} else {\n\t\t\t\terr = s.opa.PutData(id, data)\n\t\t\t\tlogrus.Infof(\"Added data %v, err=%v\", id, err)\n\t\t\t}\n\t\t}\n\t\tif err != nil {\n\t\t\tsyncErr = append(syncErr, err)\n\t\t}\n\t}\n\tif syncErr != nil {\n\t\tvar retries int = 0\n\t\tif isPolicy {\n\t\t\tif rStr, ok := cm.Annotations[retriesAnnotationKey]; ok {\n\t\t\t\tr, err := strconv.Atoi(rStr)\n\t\t\t\tif err == nil && r > 0 {\n\t\t\t\t\tretries = r - 1\n\t\t\t\t\tlogrus.Debugf(\"Adding policies error cm=%v, old retry=%v, new retry=%v\", path, rStr, retries)\n\t\t\t\t} else if err == nil && r == 0 {\n\t\t\t\t\tretries = defaultRetries\n\t\t\t\t\tlogrus.Debugf(\"Adding policies error cm=%v, old retry=%v, new retry=%v\", path, rStr, retries)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tretries = defaultRetries\n\t\t\t\tlogrus.Debugf(\"Adding policies error cm=%v, no retry annotation, new retry=%v\", path, retries)\n\t\t\t}\n\t\t}\n\t\ts.setAnnotations(cm, status{\n\t\t\tStatus: \"error\",\n\t\t\tError:  syncErr,\n\t\t}, retries)\n\t} else {\n\t\ts.setAnnotations(cm, status{\n\t\t\tStatus: \"ok\",\n\t\t}, 0)\n\t}\n}\n\nfunc (s *Sync) syncRemove(cm *v1.ConfigMap, isPolicy bool) {\n\tlogrus.Debugf(\"Attempting to remove cm=%v/%v, isPolicy=%v\", cm.Namespace, cm.Name, isPolicy)\n\tpath := fmt.Sprintf(\"%v/%v\", cm.Namespace, cm.Name)\n\tfor key := range cm.Data {\n\t\tid := fmt.Sprintf(\"%v/%v\", path, key)\n\t\tif isPolicy {\n\t\t\tif err := s.opa.DeletePolicy(id); err != nil {\n\t\t\t\tlogrus.Errorf(\"Failed to delete policy %v: %v\", id, err)\n\t\t\t}\n\t\t} else {\n\t\t\tif err := s.opa.PatchData(path, \"remove\", nil); err != nil {\n\t\t\t\tlogrus.Errorf(\"Failed to remove %v (will reset OPA data and resync in %v): %v\", id, resyncPeriod, err)\n\t\t\t\ts.syncReset(id)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc (s *Sync) setAnnotations(cm *v1.ConfigMap, st status, retries int) {\n\tbs, err := json.Marshal(st)\n\tif err != nil {\n\t\tlogrus.Errorf(\"Failed to serialize status for cm=%v/%v, err=%v\", cm.Namespace, cm.Name, err)\n\t\treturn\n\t}\n\tpatch := map[string]interface{}{\n\t\t\"metadata\": map[string]interface{}{\n\t\t\t\"annotations\": map[string]interface{}{\n\t\t\t\tstatusAnnotationKey:  string(bs),\n\t\t\t\tretriesAnnotationKey: strconv.Itoa(retries),\n\t\t\t},\n\t\t},\n\t}\n\tbs, err = json.Marshal(patch)\n\tif err != nil {\n\t\tlogrus.Errorf(\"Failed to serialize patch for %v/%v: %v\", cm.Namespace, cm.Name, err)\n\t\treturn\n\t}\n\t_, err = s.clientset.CoreV1().ConfigMaps(cm.Namespace).Patch(context.TODO(), cm.Name, types.StrategicMergePatchType, bs, metav1.PatchOptions{})\n\tif err != nil {\n\t\tlogrus.Errorf(\"Failed to %v for %v/%v: %v\", statusAnnotationKey, cm.Namespace, cm.Name, err)\n\t}\n}\n\nfunc (s *Sync) syncReset(id string) {\n\tlogrus.Debugf(\"Attempting to reset %v\", id)\n\td := syncResetBackoffMin\n\tfor {\n\t\tif err := s.opa.PutData(\"/\", map[string]interface{}{}); err != nil {\n\t\t\tlogrus.Errorf(\"Failed to reset OPA data for %v (will retry after %v): %v\", id, d, err)\n\t\t} else {\n\t\t\treturn\n\t\t}\n\t\ttime.Sleep(d)\n\t\td = d * 2\n\t\tif d > syncResetBackoffMax {\n\t\t\td = syncResetBackoffMax\n\t\t}\n\t}\n}\n\n// fingerprint for the labels and data of a configmap.\nfunc fingerprint(cm *v1.ConfigMap) uint64 {\n\thash := fnv.New64a()\n\tdata := json.NewEncoder(hash)\n\tdata.Encode(cm.Labels)\n\tdata.Encode(cm.Data)\n\treturn hash.Sum64()\n}\n\n// errList is an error type that can marshal a list of errors to json\ntype errList []error\n\nvar (\n\t// Make sure we implement the proper interfaces\n\t_ error          = errList{}\n\t_ json.Marshaler = errList{}\n)\n\ntype status struct {\n\tStatus string  `json:\"status\"`\n\tError  errList `json:\"error,omitempty\"`\n}\n\n// MarshalJSON implements json.Marshaler\nfunc (m errList) MarshalJSON() ([]byte, error) {\n\tif len(m) <= 0 {\n\t\treturn []byte(`\"\"`), nil\n\t}\n\tlist := make([]json.RawMessage, 0, len(m))\n\tfor _, err := range m {\n\t\tif b, marshalErr := json.Marshal(err); marshalErr == nil {\n\t\t\tlist = append(list, b)\n\t\t} else {\n\t\t\t// fallback to quoted .Error() string if marshalling fails\n\t\t\tlist = append(list, []byte(fmt.Sprintf(\"%q\", err.Error())))\n\t\t}\n\t}\n\tif len(list) == 1 {\n\t\treturn list[0], nil // for backward compatibility\n\t}\n\treturn json.Marshal(list)\n}\n\n// Error implements error\nfunc (m errList) Error() string {\n\tif len(m) <= 0 {\n\t\treturn \"\"\n\t}\n\ttext := make([]string, 0, len(m))\n\tfor _, err := range m {\n\t\ttext = append(text, err.Error())\n\t}\n\treturn strings.Join(text, \"\\n\")\n}\n"
  },
  {
    "path": "pkg/data/generic.go",
    "content": "// Copyright 2017 The OPA Authors.  All rights reserved.\n// Use of this source code is governed by an Apache2\n// license that can be found in the LICENSE file.\n\npackage data\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\topa_client \"github.com/open-policy-agent/kube-mgmt/pkg/opa\"\n\t\"github.com/open-policy-agent/kube-mgmt/pkg/types\"\n\n\t\"github.com/sirupsen/logrus\"\n\n\t\"k8s.io/apimachinery/pkg/api/meta\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/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/wait\"\n\t\"k8s.io/apimachinery/pkg/watch\"\n\t\"k8s.io/client-go/dynamic\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/tools/cache\"\n\t\"k8s.io/client-go/util/workqueue\"\n)\n\n// The min/max amount of time to wait when resetting the synchronizer.\nconst (\n\tbackoffMax   = time.Second * 30\n\tbackoffMin   = time.Second\n\tjitterFactor = 1.2\n\tFieldMeta    = \"metadata.namespace!=\"\n)\n\n// GenericSync replicates Kubernetes resources into OPA as raw JSON.\ntype GenericSync struct {\n\tcreateError      error // to support deprecated calls to New / Run\n\tclient           dynamicClient\n\topa              opa_client.Data\n\tns               types.ResourceType\n\tlimiter          workqueue.TypedRateLimiter[any]\n\tjitterFactor     float64\n\tignoreNamespaces []string\n\tmu               sync.Mutex\n\tready            bool\n}\n\n// New returns a new GenericSync that can be started.\n// Deprecated: Please Use NewFromInterface instead.\nfunc New(kubeconfig *rest.Config, opa opa_client.Data, ns types.ResourceType) *GenericSync {\n\tclient, err := dynamic.NewForConfig(kubeconfig)\n\tif err != nil {\n\t\treturn &GenericSync{createError: err}\n\t}\n\treturn NewFromInterface(client, opa, ns)\n}\n\ntype Option func(s *GenericSync)\n\n// NewFromInterface returns a new GenericSync that can be started.\nfunc NewFromInterface(client dynamic.Interface, opa opa_client.Data, ns types.ResourceType, opts ...Option) *GenericSync {\n\ts := &GenericSync{\n\t\tclient:       dynamicClient{client},\n\t\tns:           ns,\n\t\topa:          opa.Prefix(ns.Resource),\n\t\tjitterFactor: jitterFactor,\n\t}\n\tfor _, opt := range opts {\n\t\topt(s)\n\t}\n\tif s.limiter == nil { // Use default rateLimiter if not configured\n\t\ts.limiter = workqueue.NewTypedItemExponentialFailureRateLimiter[any](backoffMin, backoffMax)\n\t}\n\treturn s\n}\n\n// WithIgnoreNamespaces provides a list of namespaces to ignore\nfunc WithIgnoreNamespaces(ignoreNamespaces []string) Option {\n\treturn func(s *GenericSync) {\n\t\ts.ignoreNamespaces = ignoreNamespaces\n\t}\n}\n\n// WithBackoff tunes the values of exponential backoff and jitter factor\nfunc WithBackoff(min, max time.Duration, jitterFactor float64) Option {\n\treturn func(s *GenericSync) {\n\t\ts.limiter = workqueue.NewTypedItemExponentialFailureRateLimiter[any](min, max)\n\t\ts.jitterFactor = jitterFactor\n\t}\n}\n\n// Run starts the synchronizer. To stop the synchronizer send a message to the\n// channel.\n// Deprecated: Please use RunContext instead.\nfunc (s *GenericSync) Run() (chan struct{}, error) {\n\n\t// To support legacy way of creating GenericSync from *rest.Config\n\tif s.createError != nil {\n\t\treturn nil, s.createError\n\t}\n\n\tquit := make(chan struct{})\n\tctx, cancel := context.WithCancel(context.Background())\n\tgo func() { // propagate cancel signal from channel to context\n\t\t<-quit\n\t\tcancel()\n\t}()\n\tgo s.RunContext(ctx)\n\treturn quit, nil\n}\n\n// RunContext starts the synchronizer in the foreground.\n// To stop the synchronizer, cancel the context.\nfunc (s *GenericSync) RunContext(ctx context.Context) error {\n\tif s.createError != nil {\n\t\treturn s.createError\n\t}\n\n\tstore, queue := s.setup(ctx)\n\tgo func() {\n\t\t<-ctx.Done()\n\t\tqueue.ShutDown()\n\t}()\n\n\ts.loop(store, queue)\n\treturn nil\n}\n\nfunc (s *GenericSync) Ready() bool {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\treturn s.ready\n}\n\n// setup the store and queue for this GenericSync instance\nfunc (s *GenericSync) setup(ctx context.Context) (cache.Store, workqueue.TypedDelayingInterface[any]) {\n\tignoreNs := s.ignoreNs()\n\n\tresource := s.client.ResourceFor(s.ns, metav1.NamespaceAll)\n\tqueue := workqueue.NewNamedDelayingQueue(s.ns.String())\n\tstore, controller := cache.NewInformerWithOptions(cache.InformerOptions{\n\t\tListerWatcher: &cache.ListWatch{\n\t\t\tListFunc: func(options metav1.ListOptions) (runtime.Object, error) {\n\t\t\t\toptions.FieldSelector = ignoreNs\n\t\t\t\treturn resource.List(ctx, options)\n\t\t\t},\n\t\t\tWatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {\n\t\t\t\toptions.FieldSelector = ignoreNs\n\t\t\t\treturn resource.Watch(ctx, options)\n\t\t\t},\n\t\t},\n\t\tObjectType:   &unstructured.Unstructured{},\n\t\tHandler:      resourceEventQueue{queue},\n\t\tResyncPeriod: 0,\n\t})\n\n\tstart, quit := time.Now(), ctx.Done()\n\tgo controller.Run(quit)\n\tfor !cache.WaitForCacheSync(quit, controller.HasSynced) {\n\t\tlogrus.Warnf(\"Failed to sync cache for %v, retrying...\", s.ns)\n\t}\n\tif controller.HasSynced() {\n\t\tlogrus.Infof(\"Initial informer sync for %v completed, took %v\", s.ns, time.Since(start))\n\t}\n\n\treturn store, queue\n}\n\nfunc (s *GenericSync) ignoreNs() string {\n\tvar ignoreNs string\n\tif !s.ns.Namespaced {\n\t\treturn ignoreNs\n\t}\n\tif len(s.ignoreNamespaces) >= 1 {\n\t\tfor _, ns := range s.ignoreNamespaces {\n\t\t\tignoreNs = FieldMeta + ns + \",\" + ignoreNs\n\t\t}\n\t}\n\tignoreNs = strings.TrimSuffix(ignoreNs, \",\")\n\treturn ignoreNs\n}\n\n// resourceEventQueue is a cache.ResourceEventHandler that queues all events\ntype resourceEventQueue struct {\n\tworkqueue.Interface\n}\n\n// OnAdd implements ResourceHandler\nfunc (q resourceEventQueue) OnAdd(obj interface{}, isInInitialList bool) {\n\tkey, err := cache.MetaNamespaceKeyFunc(obj)\n\tif err != nil {\n\t\tlogrus.Warnf(\"failed to retrieve key: %v\", err)\n\t\treturn\n\t}\n\tq.Add(key)\n}\n\nfunc (q resourceEventQueue) resourceVersionMatch(oldObj, newObj interface{}) bool {\n\tvar (\n\t\toldMeta metav1.Object\n\t\tnewMeta metav1.Object\n\t\terr     error\n\t)\n\toldMeta, err = meta.Accessor(oldObj)\n\tif err == nil {\n\t\tnewMeta, err = meta.Accessor(newObj)\n\t}\n\tif err != nil {\n\t\tlogrus.Warnf(\"failed to retrieve meta: %v\", err)\n\t\treturn false\n\t}\n\treturn newMeta.GetResourceVersion() == oldMeta.GetResourceVersion()\n}\n\n// OnUpdate implements ResourceHandler\nfunc (q resourceEventQueue) OnUpdate(oldObj, newObj interface{}) {\n\tif !q.resourceVersionMatch(oldObj, newObj) { // Avoid sync flood on relist. We don't use resync.\n\t\tq.OnAdd(newObj, false)\n\t}\n}\n\n// OnDelete implements ResourceHandler\nfunc (q resourceEventQueue) OnDelete(obj interface{}) {\n\tkey, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)\n\tif err != nil {\n\t\tlogrus.Warnf(\"failed to retrieve key: %v\", err)\n\t\treturn\n\t}\n\tq.Add(key)\n}\n\nconst initPath = \"\"\n\n// loop starts replicating Kubernetes resources into OPA. If an error occurs\n// during the replication process, this function will backoff and reload\n// all resources into OPA from scratch.\nfunc (s *GenericSync) loop(store cache.Store, queue workqueue.TypedDelayingInterface[any]) {\n\n\tlogrus.Infof(\"Syncing %v.\", s.ns)\n\tdefer func() {\n\t\tlogrus.Infof(\"Sync for %v finished. Exiting.\", s.ns)\n\t}()\n\n\tvar delay time.Duration\n\tfor !queue.ShuttingDown() {\n\n\t\tqueue.AddAfter(initPath, delay) // this special path will trigger a full load\n\t\tsyncDone := false               // discard everything until initPath\n\n\t\tvar err error\n\t\tfor err == nil {\n\t\t\tkey, shuttingDown := queue.Get()\n\t\t\tif shuttingDown {\n\t\t\t\treturn\n\t\t\t}\n\t\t\terr = s.processNext(store, key.(string), &syncDone)\n\t\t\tif key == initPath && syncDone {\n\t\t\t\ts.limiter.Forget(initPath)\n\t\t\t}\n\t\t\tqueue.Done(key)\n\t\t}\n\n\t\tdelay := wait.Jitter(s.limiter.When(initPath), s.jitterFactor)\n\t\tlogrus.Errorf(\"Sync for %v failed, trying again in %v. Reason: %v\", s.ns, delay, err)\n\t}\n}\n\nfunc (s *GenericSync) processNext(store cache.Store, path string, syncDone *bool) error {\n\n\t// On receiving the initPath, load a full dump of the data store\n\tif path == initPath {\n\t\tif *syncDone {\n\t\t\treturn nil\n\t\t}\n\t\tstart, list := time.Now(), store.List()\n\t\tif err := s.syncAll(list); err != nil {\n\t\t\treturn err\n\t\t}\n\t\ts.mu.Lock()\n\t\ts.ready = true\n\t\ts.mu.Unlock()\n\t\tlogrus.Infof(\"Loaded %d resources of kind %v into OPA. Took %v\", len(list), s.ns, time.Since(start))\n\t\t*syncDone = true // sync is now Done\n\t\treturn nil\n\t}\n\n\t// Ignore updates queued before the initial load\n\tif !*syncDone {\n\t\treturn nil\n\t}\n\n\tobj, exists, err := store.GetByKey(path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"store error: %w\", err)\n\t}\n\tif exists {\n\t\tif err := s.opa.PutData(path, obj); err != nil {\n\t\t\treturn fmt.Errorf(\"add event: %w\", err)\n\t\t}\n\t} else {\n\t\tif err := s.opa.PatchData(path, \"remove\", nil); err != nil {\n\t\t\treturn fmt.Errorf(\"delete event: %w\", err)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *GenericSync) syncAll(objs []interface{}) error {\n\n\t// Build a list of patches to apply.\n\tpayload, err := generateSyncPayload(objs, s.ns.Namespaced)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn s.opa.PutData(\"/\", payload)\n}\n\nfunc generateSyncPayload(objs []interface{}, namespaced bool) (map[string]interface{}, error) {\n\tcombined := make(map[string]interface{}, len(objs))\n\tfor _, obj := range objs {\n\t\tpath, err := cache.MetaNamespaceKeyFunc(obj)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\n\t\t// Ensure the path in the map up to our value exists\n\t\t// We make some assumptions about the paths that do exist\n\t\t// being the correct types due to the expected uniform\n\t\t// paths for each of the similar object types being\n\t\t// sync'd with the GenericSync instance.\n\t\tsegments := strings.Split(path, \"/\")\n\t\tdir := combined\n\t\tfor i := 0; i < len(segments)-1; i++ {\n\t\t\tnext, ok := combined[segments[i]]\n\t\t\tif !ok {\n\t\t\t\tnext = map[string]interface{}{}\n\t\t\t\tdir[segments[i]] = next\n\t\t\t}\n\t\t\tdir = next.(map[string]interface{})\n\t\t}\n\t\tdir[segments[len(segments)-1]] = obj\n\t}\n\n\treturn combined, nil\n}\n"
  },
  {
    "path": "pkg/data/generic_test.go",
    "content": "package data\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/open-policy-agent/kube-mgmt/internal/expect\"\n\t\"github.com/open-policy-agent/kube-mgmt/pkg/types\"\n\n\tapiv1 \"k8s.io/api/core/v1\"\n\t\"k8s.io/apimachinery/pkg/api/meta\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/client-go/dynamic/fake\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n)\n\ntype testCase struct {\n\tLabel        string\n\tResourceType types.ResourceType\n\tPrefix       string\n\tObjs         []runtime.Object\n\tExpected     string\n}\n\n// NewFakeDynamicClient builds a new FakeDynamicClient\nfunc newFakeDynamicClient(t *testing.T, objs ...runtime.Object) dynamicClient {\n\tsc := runtime.NewScheme()\n\tif err := scheme.AddToScheme(sc); err != nil {\n\t\tt.Fatalf(\"Failed to build initial scheme: %v\", err)\n\t}\n\treturn dynamicClient{resourceInterface: fake.NewSimpleDynamicClient(sc, objs...)}\n}\n\nfunc TestGenericSync(t *testing.T) {\n\tt.Parallel()\n\n\ttestCases := []testCase{\n\t\t{\n\t\t\tLabel: \"Single Cluster Resource\",\n\t\t\tResourceType: types.ResourceType{\n\t\t\t\tNamespaced: false,\n\t\t\t\tResource:   \"nodes\",\n\t\t\t\tVersion:    \"v1\",\n\t\t\t},\n\t\t\tPrefix: \"\",\n\t\t\tObjs: []runtime.Object{\n\t\t\t\t&apiv1.Node{\n\t\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\t\t\tKind:       \"Node\",\n\t\t\t\t\t},\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:            \"node1\",\n\t\t\t\t\t\tResourceVersion: \"0\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec:   apiv1.NodeSpec{},\n\t\t\t\t\tStatus: apiv1.NodeStatus{},\n\t\t\t\t},\n\t\t\t},\n\t\t\tExpected: `{\n\t\t\t\t\"node1\":{\n\t\t\t\t\t\"apiVersion\": \"v1\",\n\t\t\t\t\t\"kind\": \"Node\",\n\t\t\t\t\t\"metadata\":{\n\t\t\t\t\t\t\"creationTimestamp\":null,\n\t\t\t\t\t\t\"name\":\"node1\",\n\t\t\t\t\t\t\"resourceVersion\":\"0\"\n\t\t\t\t\t},\n\t\t\t\t\t\"spec\":{\n\t\t\t\t\t},\n\t\t\t\t\t\"status\":{\n\t\t\t\t\t\t\"daemonEndpoints\":{\n\t\t\t\t\t\t\t\"kubeletEndpoint\":{\n\t\t\t\t\t\t\t\t\"Port\":0\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"nodeInfo\":{\n\t\t\t\t\t\t\t\"architecture\":\"\",\n\t\t\t\t\t\t\t\"bootID\":\"\",\n\t\t\t\t\t\t\t\"containerRuntimeVersion\":\"\",\n\t\t\t\t\t\t\t\"kernelVersion\":\"\",\n\t\t\t\t\t\t\t\"kubeProxyVersion\":\"\",\n\t\t\t\t\t\t\t\"kubeletVersion\":\"\",\n\t\t\t\t\t\t\t\"machineID\":\"\",\n\t\t\t\t\t\t\t\"operatingSystem\":\"\",\n\t\t\t\t\t\t\t\"osImage\":\"\",\n\t\t\t\t\t\t\t\"systemUUID\":\"\"\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},\n\t\t{\n\t\t\tLabel: \"Single Cluster Resource With Prefix\",\n\t\t\tResourceType: types.ResourceType{\n\t\t\t\tNamespaced: false,\n\t\t\t\tResource:   \"nodes\",\n\t\t\t\tVersion:    \"v1\",\n\t\t\t},\n\t\t\tPrefix: \"kube\",\n\t\t\tObjs: []runtime.Object{\n\t\t\t\t&apiv1.Node{\n\t\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\t\t\tKind:       \"Node\",\n\t\t\t\t\t},\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:            \"node1\",\n\t\t\t\t\t\tResourceVersion: \"0\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec:   apiv1.NodeSpec{},\n\t\t\t\t\tStatus: apiv1.NodeStatus{},\n\t\t\t\t},\n\t\t\t},\n\t\t\tExpected: `{\n\t\t\t\t\"node1\":{\n\t\t\t\t\t\"apiVersion\": \"v1\",\n\t\t\t\t\t\"kind\": \"Node\",\n\t\t\t\t\t\"metadata\":{\n\t\t\t\t\t\t\"creationTimestamp\":null,\n\t\t\t\t\t\t\"name\":\"node1\",\n\t\t\t\t\t\t\"resourceVersion\":\"0\"\n\t\t\t\t\t},\n\t\t\t\t\t\"spec\":{\n\t\t\t\t\t},\n\t\t\t\t\t\"status\":{\n\t\t\t\t\t\t\"daemonEndpoints\":{\n\t\t\t\t\t\t\t\"kubeletEndpoint\":{\n\t\t\t\t\t\t\t\t\"Port\":0\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"nodeInfo\":{\n\t\t\t\t\t\t\t\"architecture\":\"\",\n\t\t\t\t\t\t\t\"bootID\":\"\",\n\t\t\t\t\t\t\t\"containerRuntimeVersion\":\"\",\n\t\t\t\t\t\t\t\"kernelVersion\":\"\",\n\t\t\t\t\t\t\t\"kubeProxyVersion\":\"\",\n\t\t\t\t\t\t\t\"kubeletVersion\":\"\",\n\t\t\t\t\t\t\t\"machineID\":\"\",\n\t\t\t\t\t\t\t\"operatingSystem\":\"\",\n\t\t\t\t\t\t\t\"osImage\":\"\",\n\t\t\t\t\t\t\t\"systemUUID\":\"\"\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},\n\t\t{\n\t\t\tLabel: \"Multiple Cluster Resources With Prefix\",\n\t\t\tResourceType: types.ResourceType{\n\t\t\t\tNamespaced: false,\n\t\t\t\tResource:   \"nodes\",\n\t\t\t\tVersion:    \"v1\",\n\t\t\t},\n\t\t\tPrefix: \"kube\",\n\t\t\tObjs: []runtime.Object{\n\t\t\t\t&apiv1.Node{\n\t\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\t\t\tKind:       \"Node\",\n\t\t\t\t\t},\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:            \"node1\",\n\t\t\t\t\t\tResourceVersion: \"0\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec:   apiv1.NodeSpec{},\n\t\t\t\t\tStatus: apiv1.NodeStatus{},\n\t\t\t\t},\n\t\t\t\t&apiv1.Node{\n\t\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\t\t\tKind:       \"Node\",\n\t\t\t\t\t},\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:            \"node2\",\n\t\t\t\t\t\tResourceVersion: \"0\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec:   apiv1.NodeSpec{},\n\t\t\t\t\tStatus: apiv1.NodeStatus{},\n\t\t\t\t},\n\t\t\t\t&apiv1.Node{\n\t\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\t\t\tKind:       \"Node\",\n\t\t\t\t\t},\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:            \"node3\",\n\t\t\t\t\t\tResourceVersion: \"0\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec:   apiv1.NodeSpec{},\n\t\t\t\t\tStatus: apiv1.NodeStatus{},\n\t\t\t\t},\n\t\t\t},\n\t\t\tExpected: `{\n\t\t\t\t\"node1\":{\n\t\t\t\t\t\"apiVersion\": \"v1\",\n\t\t\t\t\t\"kind\": \"Node\",\n\t\t\t\t\t\"metadata\":{\n\t\t\t\t\t\t\"creationTimestamp\":null,\n\t\t\t\t\t\t\"name\":\"node1\",\n\t\t\t\t\t\t\"resourceVersion\":\"0\"\n\t\t\t\t\t},\n\t\t\t\t\t\"spec\":{\n\t\t\t\t\t},\n\t\t\t\t\t\"status\":{\n\t\t\t\t\t\t\"daemonEndpoints\":{\n\t\t\t\t\t\t\t\"kubeletEndpoint\":{\n\t\t\t\t\t\t\t\t\"Port\":0\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"nodeInfo\":{\n\t\t\t\t\t\t\t\"architecture\":\"\",\n\t\t\t\t\t\t\t\"bootID\":\"\",\n\t\t\t\t\t\t\t\"containerRuntimeVersion\":\"\",\n\t\t\t\t\t\t\t\"kernelVersion\":\"\",\n\t\t\t\t\t\t\t\"kubeProxyVersion\":\"\",\n\t\t\t\t\t\t\t\"kubeletVersion\":\"\",\n\t\t\t\t\t\t\t\"machineID\":\"\",\n\t\t\t\t\t\t\t\"operatingSystem\":\"\",\n\t\t\t\t\t\t\t\"osImage\":\"\",\n\t\t\t\t\t\t\t\"systemUUID\":\"\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t\"node2\":{\n\t\t\t\t\t\"apiVersion\": \"v1\",\n\t\t\t\t\t\"kind\": \"Node\",\n\t\t\t\t\t\"metadata\":{\n\t\t\t\t\t\t\"creationTimestamp\":null,\n\t\t\t\t\t\t\"name\":\"node2\",\n\t\t\t\t\t\t\"resourceVersion\":\"0\"\n\t\t\t\t\t},\n\t\t\t\t\t\"spec\":{\n\t\t\t\t\t},\n\t\t\t\t\t\"status\":{\n\t\t\t\t\t\t\"daemonEndpoints\":{\n\t\t\t\t\t\t\t\"kubeletEndpoint\":{\n\t\t\t\t\t\t\t\t\"Port\":0\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"nodeInfo\":{\n\t\t\t\t\t\t\t\"architecture\":\"\",\n\t\t\t\t\t\t\t\"bootID\":\"\",\n\t\t\t\t\t\t\t\"containerRuntimeVersion\":\"\",\n\t\t\t\t\t\t\t\"kernelVersion\":\"\",\n\t\t\t\t\t\t\t\"kubeProxyVersion\":\"\",\n\t\t\t\t\t\t\t\"kubeletVersion\":\"\",\n\t\t\t\t\t\t\t\"machineID\":\"\",\n\t\t\t\t\t\t\t\"operatingSystem\":\"\",\n\t\t\t\t\t\t\t\"osImage\":\"\",\n\t\t\t\t\t\t\t\"systemUUID\":\"\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t\"node3\":{\n\t\t\t\t\t\"apiVersion\": \"v1\",\n\t\t\t\t\t\"kind\": \"Node\",\n\t\t\t\t\t\"metadata\":{\n\t\t\t\t\t\t\"creationTimestamp\":null,\n\t\t\t\t\t\t\"name\":\"node3\",\n\t\t\t\t\t\t\"resourceVersion\":\"0\"\n\t\t\t\t\t},\n\t\t\t\t\t\"spec\":{\n\t\t\t\t\t},\n\t\t\t\t\t\"status\":{\n\t\t\t\t\t\t\"daemonEndpoints\":{\n\t\t\t\t\t\t\t\"kubeletEndpoint\":{\n\t\t\t\t\t\t\t\t\"Port\":0\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"nodeInfo\":{\n\t\t\t\t\t\t\t\"architecture\":\"\",\n\t\t\t\t\t\t\t\"bootID\":\"\",\n\t\t\t\t\t\t\t\"containerRuntimeVersion\":\"\",\n\t\t\t\t\t\t\t\"kernelVersion\":\"\",\n\t\t\t\t\t\t\t\"kubeProxyVersion\":\"\",\n\t\t\t\t\t\t\t\"kubeletVersion\":\"\",\n\t\t\t\t\t\t\t\"machineID\":\"\",\n\t\t\t\t\t\t\t\"operatingSystem\":\"\",\n\t\t\t\t\t\t\t\"osImage\":\"\",\n\t\t\t\t\t\t\t\"systemUUID\":\"\"\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},\n\t\t{\n\t\t\tLabel: \"Single Namespaced Resource\",\n\t\t\tResourceType: types.ResourceType{\n\t\t\t\tNamespaced: true,\n\t\t\t\tResource:   \"pods\",\n\t\t\t\tVersion:    \"v1\",\n\t\t\t},\n\t\t\tPrefix: \"\",\n\t\t\tObjs: []runtime.Object{\n\t\t\t\t&apiv1.Pod{\n\t\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\t\t\tKind:       \"Pod\",\n\t\t\t\t\t},\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:            \"pod1\",\n\t\t\t\t\t\tNamespace:       \"ns1\",\n\t\t\t\t\t\tResourceVersion: \"0\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec:   apiv1.PodSpec{},\n\t\t\t\t\tStatus: apiv1.PodStatus{},\n\t\t\t\t},\n\t\t\t},\n\t\t\tExpected: `{\n\t\t\t\t\"ns1\":{\n\t\t\t\t\t\"pod1\":{\n\t\t\t\t\t\t\"apiVersion\": \"v1\",\n\t\t\t\t\t\t\"kind\": \"Pod\",\n\t\t\t\t\t\t\"metadata\":{\n\t\t\t\t\t\t\t\"creationTimestamp\":null,\n\t\t\t\t\t\t\t\"name\":\"pod1\",\n\t\t\t\t\t\t\t\"namespace\":\"ns1\",\n\t\t\t\t\t\t\t\"resourceVersion\":\"0\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"spec\":{\n\t\t\t\t\t\t\t\"containers\":null\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"status\":{\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},\n\t\t{\n\t\t\tLabel: \"Single Namespaced Resource With Prefix\",\n\t\t\tResourceType: types.ResourceType{\n\t\t\t\tNamespaced: true,\n\t\t\t\tResource:   \"pods\",\n\t\t\t\tVersion:    \"v1\",\n\t\t\t},\n\t\t\tPrefix: \"kube\",\n\t\t\tObjs: []runtime.Object{\n\t\t\t\t&apiv1.Pod{\n\t\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\t\t\tKind:       \"Pod\",\n\t\t\t\t\t},\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:            \"pod1\",\n\t\t\t\t\t\tNamespace:       \"ns1\",\n\t\t\t\t\t\tResourceVersion: \"0\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec:   apiv1.PodSpec{},\n\t\t\t\t\tStatus: apiv1.PodStatus{},\n\t\t\t\t},\n\t\t\t},\n\t\t\tExpected: `{\n\t\t\t\t\"ns1\":{\n\t\t\t\t\t\"pod1\":{\n\t\t\t\t\t\t\"apiVersion\": \"v1\",\n\t\t\t\t\t\t\"kind\": \"Pod\",\n\t\t\t\t\t\t\"metadata\":{\n\t\t\t\t\t\t\t\"creationTimestamp\":null,\n\t\t\t\t\t\t\t\"name\":\"pod1\",\n\t\t\t\t\t\t\t\"namespace\":\"ns1\",\n\t\t\t\t\t\t\t\"resourceVersion\":\"0\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"spec\":{\n\t\t\t\t\t\t\t\"containers\":null\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"status\":{\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},\n\t\t{\n\t\t\tLabel: \"Multiple Namespaced Resources With Prefix\",\n\t\t\tResourceType: types.ResourceType{\n\t\t\t\tNamespaced: true,\n\t\t\t\tResource:   \"pods\",\n\t\t\t\tVersion:    \"v1\",\n\t\t\t},\n\t\t\tPrefix: \"kube\",\n\t\t\tObjs: []runtime.Object{\n\t\t\t\t&apiv1.Pod{\n\t\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\t\t\tKind:       \"Pod\",\n\t\t\t\t\t},\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:            \"pod1\",\n\t\t\t\t\t\tNamespace:       \"ns1\",\n\t\t\t\t\t\tResourceVersion: \"0\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec:   apiv1.PodSpec{},\n\t\t\t\t\tStatus: apiv1.PodStatus{},\n\t\t\t\t},\n\t\t\t\t&apiv1.Pod{\n\t\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\t\t\tKind:       \"Pod\",\n\t\t\t\t\t},\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:            \"pod2\",\n\t\t\t\t\t\tNamespace:       \"ns1\",\n\t\t\t\t\t\tResourceVersion: \"0\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec:   apiv1.PodSpec{},\n\t\t\t\t\tStatus: apiv1.PodStatus{},\n\t\t\t\t},\n\t\t\t\t&apiv1.Pod{\n\t\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\t\t\tKind:       \"Pod\",\n\t\t\t\t\t},\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tName:            \"pod1\",\n\t\t\t\t\t\tNamespace:       \"ns2\",\n\t\t\t\t\t\tResourceVersion: \"0\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec:   apiv1.PodSpec{},\n\t\t\t\t\tStatus: apiv1.PodStatus{},\n\t\t\t\t},\n\t\t\t},\n\t\t\tExpected: `{\n\t\t\t\t\"ns1\":{\n\t\t\t\t\t\"pod1\":{\n\t\t\t\t\t\t\"apiVersion\": \"v1\",\n\t\t\t\t\t\t\"kind\": \"Pod\",\n\t\t\t\t\t\t\"metadata\":{\n\t\t\t\t\t\t\t\"creationTimestamp\":null,\n\t\t\t\t\t\t\t\"name\":\"pod1\",\n\t\t\t\t\t\t\t\"namespace\":\"ns1\",\n\t\t\t\t\t\t\t\"resourceVersion\":\"0\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"spec\":{\n\t\t\t\t\t\t\t\"containers\":null\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"status\":{\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\t\"pod2\":{\n\t\t\t\t\t\t\"apiVersion\": \"v1\",\n\t\t\t\t\t\t\"kind\": \"Pod\",\n\t\t\t\t\t\t\"metadata\":{\n\t\t\t\t\t\t\t\"creationTimestamp\":null,\n\t\t\t\t\t\t\t\"name\":\"pod2\",\n\t\t\t\t\t\t\t\"namespace\":\"ns1\",\n\t\t\t\t\t\t\t\"resourceVersion\":\"0\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"spec\":{\n\t\t\t\t\t\t\t\"containers\":null\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"status\":{\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t\"ns2\":{\n\t\t\t\t\t\"pod1\": {\n\t\t\t\t\t\t\"apiVersion\": \"v1\",\n\t\t\t\t\t\t\"kind\": \"Pod\",\n\t\t\t\t\t\t\"metadata\":{\n\t\t\t\t\t\t\t\"creationTimestamp\":null,\n\t\t\t\t\t\t\t\"name\":\"pod1\",\n\t\t\t\t\t\t\t\"namespace\":\"ns2\",\n\t\t\t\t\t\t\t\"resourceVersion\":\"0\"\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"spec\":{\n\t\t\t\t\t\t\t\"containers\":null\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"status\":{\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},\n\t}\n\n\tfor _, tc := range testCases {\n\n\t\ttc := tc // We will be running the tests in parallel, so avoid issues with loop var\n\t\texpected := expect.MustMarshal(t, expect.MustUnmarshal(t, []byte(tc.Expected)))\n\n\t\tt.Run(fmt.Sprintf(\"%s - Must Generate Sync Payload\", tc.Label), func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\ttc.testGenerateSyncPayload(t, expected)\n\t\t})\n\n\t\tt.Run(fmt.Sprintf(\"%s - Must Load Existing Resources\", tc.Label), func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\ttc.testLoad(t, expected)\n\t\t})\n\n\t\tt.Run(fmt.Sprintf(\"%s - Must Add New Resources\", tc.Label), func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\ttc.testAdd(t)\n\t\t})\n\n\t\tt.Run(fmt.Sprintf(\"%s - Must Remove Resources\", tc.Label), func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\ttc.testDelete(t)\n\t\t})\n\n\t\tt.Run(fmt.Sprintf(\"%s - Must Update Resources\", tc.Label), func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\ttc.testUpdate(t)\n\t\t})\n\n\t\tt.Run(fmt.Sprintf(\"%s - Must Retry Load On Error\", tc.Label), func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\ttc.testRetryLoad(t, expected)\n\t\t})\n\n\t\tt.Run(fmt.Sprintf(\"%s - Must Retry Add On Error\", tc.Label), func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\ttc.testRetryAdd(t)\n\t\t})\n\n\t\tt.Run(fmt.Sprintf(\"%s - Must Retry Update On Error\", tc.Label), func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\ttc.testRetryUpdate(t)\n\t\t})\n\n\t\tt.Run(fmt.Sprintf(\"%s - Must Retry Delete On Error\", tc.Label), func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\ttc.testRetryDelete(t)\n\t\t})\n\t}\n}\n\nfunc TestEventQueue(t *testing.T) {\n\tt.Parallel()\n\n\ttc := testCase{\n\t\tLabel: \"Single Cluster Resource\",\n\t\tResourceType: types.ResourceType{\n\t\t\tNamespaced: false,\n\t\t\tResource:   \"nodes\",\n\t\t\tVersion:    \"v1\",\n\t\t},\n\t\tPrefix: \"\",\n\t\tObjs: []runtime.Object{\n\t\t\t&apiv1.Node{\n\t\t\t\tTypeMeta: metav1.TypeMeta{\n\t\t\t\t\tAPIVersion: \"v1\",\n\t\t\t\t\tKind:       \"Node\",\n\t\t\t\t},\n\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\tName:            \"node1\",\n\t\t\t\t\tResourceVersion: \"0\",\n\t\t\t\t},\n\t\t\t\tSpec:   apiv1.NodeSpec{},\n\t\t\t\tStatus: apiv1.NodeStatus{},\n\t\t\t},\n\t\t},\n\t\tExpected: `{\n\t\t\t\t\"node1\":{\n\t\t\t\t\t\"apiVersion\": \"v1\",\n\t\t\t\t\t\"kind\": \"Node\",\n\t\t\t\t\t\"metadata\":{\n\t\t\t\t\t\t\"creationTimestamp\":null,\n\t\t\t\t\t\t\"name\":\"node1\",\n\t\t\t\t\t\t\"resourceVersion\":\"0\"\n\t\t\t\t\t},\n\t\t\t\t\t\"spec\":{\n\t\t\t\t\t},\n\t\t\t\t\t\"status\":{\n\t\t\t\t\t\t\"daemonEndpoints\":{\n\t\t\t\t\t\t\t\"kubeletEndpoint\":{\n\t\t\t\t\t\t\t\t\"Port\":0\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t\"nodeInfo\":{\n\t\t\t\t\t\t\t\"architecture\":\"\",\n\t\t\t\t\t\t\t\"bootID\":\"\",\n\t\t\t\t\t\t\t\"containerRuntimeVersion\":\"\",\n\t\t\t\t\t\t\t\"kernelVersion\":\"\",\n\t\t\t\t\t\t\t\"kubeProxyVersion\":\"\",\n\t\t\t\t\t\t\t\"kubeletVersion\":\"\",\n\t\t\t\t\t\t\t\"machineID\":\"\",\n\t\t\t\t\t\t\t\"operatingSystem\":\"\",\n\t\t\t\t\t\t\t\"osImage\":\"\",\n\t\t\t\t\t\t\t\"systemUUID\":\"\"\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}`,\n\t}\n\n\tt.Run(fmt.Sprintf(\"%s - Must Update On Different ResourceVersion\", tc.Label), func(t *testing.T) {\n\t\tt.Parallel()\n\t\ttc.testUpdateDifferentVersion(t)\n\t})\n\n\tt.Run(fmt.Sprintf(\"%s - Must Skip On Same ResourceVersion\", tc.Label), func(t *testing.T) {\n\t\tt.Parallel()\n\t\ttc.testUpdateSameVersion(t)\n\t})\n}\n\nfunc (tc *testCase) testGenerateSyncPayload(t *testing.T, expected []byte) {\n\n\tdata := make([]interface{}, 0, len(tc.Objs))\n\tfor _, obj := range tc.Objs {\n\t\tdata = append(data, obj)\n\t}\n\n\tpatches, err := generateSyncPayload(data, tc.ResourceType.Namespaced)\n\tif err != nil {\n\t\tt.Fatalf(\"Unexpected error: %v\", err)\n\t}\n\n\tresult := expect.MustRoundTrip(t, patches)\n\texpect.MustEqual(t, result, expected)\n}\n\nfunc (tc *testCase) Play(t *testing.T, client dynamicClient, play expect.Script) *expect.Client {\n\tt.Helper()\n\n\treturn expect.Play(t, play, func(ctx context.Context, mockClient *expect.Client) {\n\t\tdata := mockClient.Prefix(tc.Prefix)\n\t\tsync := NewFromInterface(\n\t\t\tclient,\n\t\t\tdata,\n\t\t\ttc.ResourceType,\n\t\t\tWithBackoff(0, 5*time.Second, 0),\n\t\t)\n\t\tsync.RunContext(ctx)\n\t})\n}\n\nfunc (tc *testCase) testLoad(t *testing.T, expected []byte) {\n\n\tclient := newFakeDynamicClient(t, tc.Objs...)\n\tplay := expect.Script{\n\t\texpect.PutData(\"/\", expected).End(),\n\t}\n\n\tdata := tc.Play(t, client, play)\n\texpect.MustEqual(t, data.PrefixList, []string{tc.Prefix, tc.ResourceType.Resource})\n}\n\nfunc (tc *testCase) testAdd(t *testing.T) {\n\n\tclient, obj := newFakeDynamicClient(t), tc.Objs[0]\n\tplay := expect.Script{\n\t\texpect.PutData(\"/\", []byte(\"{}\")).Do(client.MustCreate(t, tc.ResourceType, obj)),\n\t\texpect.PutData(expect.MustKey(t, obj), expect.MustRoundTrip(t, obj)).End(),\n\t}\n\n\ttc.Play(t, client, play)\n}\n\nfunc (tc *testCase) testDelete(t *testing.T) {\n\n\tclient, obj := newFakeDynamicClient(t, tc.Objs...), tc.Objs[0]\n\tplay := expect.Script{\n\t\texpect.PutData(\"/\").Do(client.MustRemove(t, tc.ResourceType, obj)),\n\t\texpect.PatchData(expect.MustKey(t, obj), \"remove\").End(),\n\t}\n\n\ttc.Play(t, client, play)\n}\n\nfunc (tc *testCase) testUpdate(t *testing.T) {\n\n\tchange := mustUnstructure(t, tc.Objs[0])\n\tchange.SetLabels(map[string]string{\"test\": \"update\"})\n\tchange.SetResourceVersion(\"1\")\n\n\tclient := newFakeDynamicClient(t, tc.Objs...)\n\tplay := expect.Script{\n\t\texpect.PutData(\"/\").Do(client.MustUpdate(t, tc.ResourceType, change)),\n\t\texpect.PutData(expect.MustKey(t, change), expect.MustRoundTrip(t, change.Object)).End(),\n\t}\n\n\ttc.Play(t, client, play)\n}\n\nfunc (tc *testCase) testRetryLoad(t *testing.T, expected []byte) {\n\n\tclient := newFakeDynamicClient(t, tc.Objs...)\n\tplay := expect.Script{\n\t\texpect.PutData(\"/\").DoError(errors.New(\"test fail update\")),\n\t\texpect.PutData(\"/\", expected).End(),\n\t}\n\n\ttc.Play(t, client, play)\n}\n\nfunc (tc *testCase) testRetryAdd(t *testing.T) {\n\n\tclient, obj := newFakeDynamicClient(t), tc.Objs[0]\n\tplay := expect.Script{\n\t\texpect.PutData(\"/\").Do(client.MustCreate(t, tc.ResourceType, obj)),\n\t\texpect.PutData(expect.MustKey(t, obj)).DoError(errors.New(\"test fail update\")),\n\t\texpect.PutData(\"/\").End(),\n\t}\n\n\ttc.Play(t, client, play)\n}\n\nfunc (tc *testCase) testRetryUpdate(t *testing.T) {\n\n\tchange := mustUnstructure(t, tc.Objs[0])\n\tchange.SetLabels(map[string]string{\"test\": \"update\"})\n\tchange.SetResourceVersion(\"1\")\n\n\tclient := newFakeDynamicClient(t, tc.Objs...)\n\tplay := expect.Script{\n\t\texpect.PutData(\"/\").Do(client.MustUpdate(t, tc.ResourceType, change)),\n\t\texpect.PutData(expect.MustKey(t, change)).DoError(errors.New(\"Failed to update\")),\n\t\texpect.PutData(\"/\").End(),\n\t\t// don't check the payload on this last put, because we\n\t\t// have removed an item so it no longer matches the tc.expected\n\t}\n\n\ttc.Play(t, client, play)\n}\n\nfunc (tc *testCase) testRetryDelete(t *testing.T) {\n\n\tclient, obj := newFakeDynamicClient(t, tc.Objs...), tc.Objs[0]\n\tplay := expect.Script{\n\t\texpect.PutData(\"/\").Do(client.MustRemove(t, tc.ResourceType, obj)),\n\t\texpect.PatchData(expect.MustKey(t, obj), \"remove\").DoError(errors.New(\"test Patch failed\")),\n\t\texpect.PutData(\"/\").End(),\n\t\t// don't check the payload on this last put, because we\n\t\t// have removed an item so it no longer matches the tc.expected\n\t}\n\n\ttc.Play(t, client, play)\n}\n\nfunc (tc *testCase) testUpdateSameVersion(t *testing.T) {\n\n\tchange := mustUnstructure(t, tc.Objs[0])\n\tchange.SetAnnotations(map[string]string{\"test\": \"update\"})\n\n\tclient := newFakeDynamicClient(t, tc.Objs...)\n\tplay := expect.Script{\n\t\texpect.PutData(\"/\").Do(client.MustUpdate(t, tc.ResourceType, change)),\n\t\texpect.Nothing(100 * time.Millisecond).End(),\n\t}\n\n\ttc.Play(t, client, play)\n}\n\nfunc (tc *testCase) testUpdateDifferentVersion(t *testing.T) {\n\n\tchange := mustUnstructure(t, tc.Objs[0])\n\tchange.SetLabels(map[string]string{\"test\": \"update\"})\n\tchange.SetResourceVersion(\"1\")\n\n\tclient := newFakeDynamicClient(t, tc.Objs...)\n\tplay := expect.Script{\n\t\texpect.PutData(\"/\").Do(client.MustUpdate(t, tc.ResourceType, change)),\n\t\texpect.PutData(expect.MustKey(t, change), expect.MustRoundTrip(t, change)).End(),\n\t}\n\n\ttc.Play(t, client, play)\n}\n\n// MustCreate returns an action that creates an instance of the resource\nfunc (f dynamicClient) MustCreate(t *testing.T, resourceType types.ResourceType, obj runtime.Object) expect.Action {\n\tnamespace := mustAccess(t, obj).GetNamespace()\n\treturn func() error {\n\t\tr := f.ResourceFor(resourceType, namespace)\n\t\tif _, err := r.Create(context.Background(), mustUnstructure(t, obj), metav1.CreateOptions{}); err != nil {\n\t\t\tt.Fatalf(\"Failed to create object %v: %v\", obj, err)\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// MustRemove returns an Action that removes an instance of the resource\nfunc (f dynamicClient) MustRemove(t *testing.T, resourceType types.ResourceType, obj runtime.Object) expect.Action {\n\tm := mustAccess(t, obj)\n\treturn func() error {\n\t\tr := f.ResourceFor(resourceType, m.GetNamespace())\n\t\tif err := r.Delete(context.Background(), m.GetName(), metav1.DeleteOptions{}); err != nil {\n\t\t\tt.Fatalf(\"Failed to remove object %v: %v\", obj, err)\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// MustUpdate returns an Action that updates an instance of the resource\nfunc (f dynamicClient) MustUpdate(t *testing.T, resourceType types.ResourceType, obj runtime.Object) expect.Action {\n\tnamespace := mustAccess(t, obj).GetNamespace()\n\treturn func() error {\n\t\tr := f.ResourceFor(resourceType, namespace)\n\t\tif _, err := r.Update(context.Background(), obj.(*unstructured.Unstructured), metav1.UpdateOptions{}); err != nil {\n\t\t\tt.Fatalf(\"Failed to create object %v: %v\", obj, err)\n\t\t}\n\t\treturn nil\n\t}\n}\n\n// mustUnstructure clones the object provided into an Unstructured object\nfunc mustUnstructure(t *testing.T, obj runtime.Object) *unstructured.Unstructured {\n\tcopiedObj := expect.MustUnmarshal(t, expect.MustMarshal(t, obj))\n\tif asMap, ok := copiedObj.(map[string]interface{}); ok {\n\t\treturn &unstructured.Unstructured{Object: asMap}\n\t}\n\tt.Fatalf(\"Failed to copy %#v as a map[string]interface{}\", obj)\n\treturn nil // to make staticcheck happy\n}\n\n// mustAccess returns an accessor for the given object\nfunc mustAccess(t *testing.T, obj runtime.Object) metav1.Object {\n\tm, err := meta.Accessor(obj)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to build accessor for %v: %v\", obj, err)\n\t}\n\treturn m\n}\n\nfunc TestGenericSync_ignoreNs(t *testing.T) {\n\ttype fields struct {\n\t\tignoreNamespaces []string\n\t}\n\ttests := []struct {\n\t\tname   string\n\t\tfields fields\n\t\twant   string\n\t\tns     types.ResourceType\n\t}{\n\t\t{\n\t\t\tname: \"empty fields\",\n\t\t\tfields: fields{\n\t\t\t\t[]string{},\n\t\t\t},\n\t\t\twant: \"\",\n\t\t\tns:   types.ResourceType{Namespaced: true},\n\t\t},\n\t\t{\n\t\t\tname: \"one field\",\n\t\t\tfields: fields{\n\t\t\t\t[]string{\"cluster-autosscaler\"},\n\t\t\t},\n\t\t\twant: \"\",\n\t\t\tns:   types.ResourceType{Namespaced: false},\n\t\t},\n\t\t{\n\t\t\tname: \"one field\",\n\t\t\tfields: fields{\n\t\t\t\t[]string{\"cluster-autosscaler\"},\n\t\t\t},\n\t\t\twant: \"metadata.namespace!=cluster-autosscaler\",\n\t\t\tns:   types.ResourceType{Namespaced: true},\n\t\t},\n\t\t{\n\t\t\tname: \"two fields\",\n\t\t\tfields: fields{\n\t\t\t\t[]string{\"cluster-autoscaler\", \"cluster-manager\"},\n\t\t\t},\n\t\t\twant: \"metadata.namespace!=cluster-manager,metadata.namespace!=cluster-autoscaler\",\n\t\t\tns:   types.ResourceType{Namespaced: true},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\ts := &GenericSync{\n\t\t\t\tignoreNamespaces: tt.fields.ignoreNamespaces,\n\t\t\t\tns:               tt.ns,\n\t\t\t}\n\t\t\tif got := s.ignoreNs(); got != tt.want {\n\t\t\t\tt.Errorf(\"GenericSync.ignoreNs() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/data/types.go",
    "content": "package data\n\nimport (\n\t\"github.com/open-policy-agent/kube-mgmt/pkg/types\"\n\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/client-go/dynamic\"\n)\n\n// resourceInterface knows how to use the method `Resource` of dynamic.Interface\ntype resourceInterface interface {\n\tResource(schema.GroupVersionResource) dynamic.NamespaceableResourceInterface\n}\n\n// dynamicClient wraps a resourceInterface with some utilities\ntype dynamicClient struct {\n\tresourceInterface\n}\n\n// ResourceFor builds a dynamic.ResourceInterface for a ResourceType\nfunc (f dynamicClient) ResourceFor(resourceType types.ResourceType, namespace string) dynamic.ResourceInterface {\n\tresource := f.Resource(schema.GroupVersionResource{\n\t\tGroup:    resourceType.Group,\n\t\tVersion:  resourceType.Version,\n\t\tResource: resourceType.Resource,\n\t})\n\tif resourceType.Namespaced {\n\t\treturn resource.Namespace(namespace)\n\t}\n\treturn resource\n}\n"
  },
  {
    "path": "pkg/dynamicdata/dynamicdata.go",
    "content": "package dynamicdata\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\t\"sort\"\n\t\"sync\"\n\n\t\"github.com/open-policy-agent/kube-mgmt/pkg/data\"\n\t\"github.com/open-policy-agent/kube-mgmt/pkg/opa\"\n\t\"github.com/open-policy-agent/kube-mgmt/pkg/types\"\n\n\t//lint:ignore SA1019 using OPA v0.x to ensure backwards compatible with pre-1.0 bundles\n\t\"github.com/open-policy-agent/opa/ast\"\n\n\t//lint:ignore SA1019 using OPA v0.x to ensure backwards compatible with pre-1.0 bundles\n\t\"github.com/open-policy-agent/opa/dependencies\"\n\n\t//lint:ignore SA1019 using OPA v0.x to ensure backwards compatible with pre-1.0 bundles\n\t\"github.com/open-policy-agent/opa/logging\"\n\n\t//lint:ignore SA1019 using OPA v0.x to ensure backwards compatible with pre-1.0 bundles\n\t\"github.com/open-policy-agent/opa/plugins\"\n\n\t//lint:ignore SA1019 using OPA v0.x to ensure backwards compatible with pre-1.0 bundles\n\t\"github.com/open-policy-agent/opa/sdk\"\n\n\t//lint:ignore SA1019 using OPA v0.x to ensure backwards compatible with pre-1.0 bundles\n\t\"github.com/open-policy-agent/opa/storage\"\n\n\t//lint:ignore SA1019 using OPA v0.x to ensure backwards compatible with pre-1.0 bundles\n\t\"github.com/open-policy-agent/opa/storage/inmem\"\n\n\t\"github.com/sirupsen/logrus\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/client-go/discovery\"\n\t\"k8s.io/client-go/dynamic\"\n\t\"k8s.io/client-go/rest\"\n)\n\ntype Sync struct {\n\topaConfig          []byte\n\tkubeconfig         *rest.Config\n\topaURL, opaAuth    string\n\tignoreNs           []string\n\tanalysisEntrypoint string\n\treplicatePath      string\n\tlogger             logging.Logger\n\trunning            map[types.ResourceType]*cancellableSync\n\tmu                 sync.Mutex\n\tready              bool\n}\n\nfunc New(configFile string, analysisEntrypoint string, opaURL, opaAuth string, ignoreNs []string, replicatePath string, kubeconfig *rest.Config, logger logging.Logger) (*Sync, error) {\n\n\tbs, err := os.ReadFile(configFile)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tsync := &Sync{\n\t\topaConfig:          bs,\n\t\tkubeconfig:         kubeconfig,\n\t\topaAuth:            opaAuth,\n\t\topaURL:             opaURL,\n\t\tignoreNs:           ignoreNs,\n\t\tanalysisEntrypoint: analysisEntrypoint,\n\t\treplicatePath:      replicatePath,\n\t\tlogger:             logger,\n\t\trunning:            make(map[types.ResourceType]*cancellableSync),\n\t}\n\n\treturn sync, nil\n}\n\nfunc (s *Sync) Run(ctx context.Context) error {\n\n\ts.logger.Debug(\"Loading kubeconfig for API server\")\n\tclient, err := dynamic.NewForConfig(s.kubeconfig)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.logger.Debug(\"Resolving resource names to resource types\")\n\trts, err := resolveResourceTypes(s.kubeconfig)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts.logger.Debug(\"Starting analyzer\")\n\tanalyzer, err := newAnalyzer(ctx, s.opaConfig, s.replicatePath, s.analysisEntrypoint, s.logger)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tgo s.loop(ctx, analyzer, rts, client)\n\n\treturn nil\n}\n\nfunc (s *Sync) Ready() bool {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\tif !s.ready {\n\t\ts.logger.Debug(\"Sync is not ready\")\n\t\treturn false\n\t}\n\tfor rt, r := range s.running {\n\t\tif !r.sync.Ready() {\n\t\t\ts.logger.Debug(\"Replicator for %v is not ready\", rt)\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\nfunc (s *Sync) loop(ctx context.Context, a *analyzer, rts map[string]types.ResourceType, client *dynamic.DynamicClient) {\n\tfor {\n\t\ts.logger.Debug(\"Sync waiting for analysis result\")\n\t\tselect {\n\t\tcase result := <-a.C:\n\t\t\ts.logger.Debug(\"Sync processing analysis result: %v\", result)\n\t\t\ts.processAnalysisResult(ctx, result, rts, client)\n\t\tcase <-ctx.Done():\n\t\t\ts.logger.Debug(\"Sync shutting down\")\n\t\t}\n\t}\n}\n\nfunc (s *Sync) processAnalysisResult(ctx context.Context, result analysisResult, rts map[string]types.ResourceType, client *dynamic.DynamicClient) {\n\ts.mu.Lock()\n\tdefer s.mu.Unlock()\n\n\t// If any of the refs cannot be mapped to gvk then give up.\n\tfor _, ref := range result.Refs {\n\t\tif _, ok := rts[ref.Resource]; !ok {\n\t\t\tlogrus.Errorf(\"Cannot resolve Kubernetes resource %q to group/version/resource for dynamic data replication\", ref.Resource)\n\t\t\ts.ready = false\n\t\t\treturn\n\t\t}\n\t}\n\n\t// Otherwise, create and delete data syncs accordingly.\n\ts.ready = true\n\tcreate := map[types.ResourceType]struct{}{}\n\n\tfor _, ref := range result.Refs {\n\t\trt := rts[ref.Resource]\n\t\tcreate[rt] = struct{}{}\n\t\tif _, ok := s.running[rt]; !ok {\n\t\t\ts.logger.Debug(\"Starting data replication for %v\", rt)\n\t\t\tsync := data.NewFromInterface(client, opa.New(s.opaURL, s.opaAuth).Prefix(s.replicatePath), rt, data.WithIgnoreNamespaces(s.ignoreNs))\n\t\t\tctx, cancel := context.WithCancel(ctx)\n\t\t\ts.running[rt] = &cancellableSync{cancel: cancel, sync: sync}\n\t\t\tgo sync.RunContext(ctx)\n\t\t} else {\n\t\t\ts.logger.Debug(\"Data replication for %v already started\", rt)\n\t\t}\n\t}\n\n\tfor rt, sync := range s.running {\n\t\tif _, ok := create[rt]; !ok {\n\t\t\ts.logger.Debug(\"Stopping replication for %v\", rt)\n\t\t\tsync.cancel()\n\t\t\tdelete(s.running, rt)\n\t\t}\n\t}\n}\n\nfunc resolveResourceTypes(config *rest.Config) (map[string]types.ResourceType, error) {\n\tdiscoveryClient, err := discovery.NewDiscoveryClientForConfig(config)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create discovery client: %v\", err)\n\t}\n\n\tresources, err := discoveryClient.ServerPreferredResources()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get server preferred resources: %v\", err)\n\t}\n\n\tresult := map[string]types.ResourceType{}\n\n\tfor _, r := range resources {\n\t\tgv, err := schema.ParseGroupVersion(r.GroupVersion)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\tfor _, ar := range r.APIResources {\n\t\t\trt := types.ResourceType{\n\t\t\t\tNamespaced: ar.Namespaced,\n\t\t\t\tResource:   ar.Name,\n\t\t\t\tGroup:      ar.Group,\n\t\t\t\tVersion:    ar.Version,\n\t\t\t}\n\t\t\tif rt.Group == \"\" {\n\t\t\t\trt.Group = gv.Group\n\t\t\t}\n\t\t\tif rt.Version == \"\" {\n\t\t\t\trt.Version = gv.Version\n\t\t\t}\n\t\t\tlogrus.Infof(\"Discovered resource %v mapping to type %v (namespaced: %v)\", ar.Name, rt, rt.Namespaced)\n\t\t\tresult[ar.Name] = rt\n\t\t}\n\t}\n\n\treturn result, nil\n}\n\ntype cancellableSync struct {\n\tcancel context.CancelFunc\n\tsync   *data.GenericSync\n}\n\ntype analyzer struct {\n\tC       chan analysisResult\n\tupdates chan *ast.Compiler\n\topa     *sdk.OPA\n\tprefix  ast.Ref\n\tentry   ast.Ref\n\tlogger  logging.Logger\n}\n\ntype analysisResult struct {\n\tRefs []ref\n}\n\nfunc newAnalyzer(ctx context.Context, bs []byte, replicatePath, analysisEntrypoint string, logger logging.Logger) (*analyzer, error) {\n\n\ta := &analyzer{\n\t\tC:       make(chan analysisResult),\n\t\tupdates: make(chan *ast.Compiler, 1),\n\t\tlogger:  logger,\n\t}\n\n\tvar err error\n\n\ta.prefix, err = ast.PtrRef(ast.DefaultRootDocument, replicatePath)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\ta.entry, err = ast.PtrRef(ast.DefaultRootDocument, analysisEntrypoint)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tgo a.loop(ctx)\n\n\tstore := inmem.New()\n\n\terr = storage.Txn(ctx, store, storage.TransactionParams{Write: true}, func(txn storage.Transaction) error {\n\t\t_, err := store.Register(ctx, txn, storage.TriggerConfig{OnCommit: a.trigger})\n\t\treturn err\n\t})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ta.opa, err = sdk.New(ctx, sdk.Options{Config: bytes.NewBuffer(bs), Store: store, Logger: logger})\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn a, nil\n}\n\ntype ref struct {\n\tResource string\n}\n\nfunc (a *analyzer) Stop(ctx context.Context) error {\n\tclose(a.C)\n\ta.opa.Stop(ctx)\n\treturn nil\n}\n\nfunc (a *analyzer) trigger(_ context.Context, txn storage.Transaction, event storage.TriggerEvent) {\n\tcompiler := plugins.GetCompilerOnContext(event.Context)\n\ta.logger.Debug(\"Analyzer received storage trigger callback (txn=%d, compiler=%p)\", txn.ID(), compiler)\n\tif compiler == nil {\n\t\treturn\n\t}\n\ta.updates <- compiler\n}\n\nfunc (a *analyzer) loop(ctx context.Context) {\n\tfor {\n\t\tselect {\n\t\tcase compiler := <-a.updates:\n\t\t\trefs, missing, err := analyzeRefs(compiler, []ast.Ref{a.entry}, a.prefix, a.logger)\n\t\t\tif err != nil {\n\t\t\t\ta.logger.Error(\"Failed to analyze refs: %v\", err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif len(missing) > 0 {\n\t\t\t\ta.logger.Debug(\"Analysis could not find entrypoints %v, skipping update\", missing)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\ta.C <- analysisResult{Refs: refs}\n\t\tcase <-ctx.Done():\n\t\t\tlogrus.Info(\"Analyzer shutting down\")\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc analyzeRefs(c *ast.Compiler, entrypoints []ast.Ref, prefix ast.Ref, logger logging.Logger) ([]ref, []ast.Ref, error) {\n\tlogger.Debug(\"Analyzing dependencies for references to %v starting from %v\", prefix, entrypoints)\n\tresultMap := map[string]struct{}{}\n\tvisited := map[*ast.Rule]struct{}{}\n\tvar queue []*ast.Rule\n\n\tmissing := []ast.Ref{}\n\n\tfor _, ref := range entrypoints {\n\t\trules := c.GetRulesForVirtualDocument(ref)\n\t\tif len(rules) == 0 {\n\t\t\tmissing = append(missing, ref)\n\t\t}\n\t\tqueue = append(queue, rules...)\n\t}\n\tif len(missing) > 0 {\n\t\treturn nil, missing, nil\n\t}\n\n\tfor len(queue) > 0 {\n\t\tvar next *ast.Rule\n\t\tnext, queue = queue[0], queue[1:]\n\t\tif _, ok := visited[next]; ok {\n\t\t\tcontinue\n\t\t}\n\t\tvisited[next] = struct{}{}\n\t\tfor a := range c.Graph.Dependencies(next) {\n\t\t\tqueue = append(queue, a.(*ast.Rule))\n\t\t}\n\t\tdeps, err := dependencies.Minimal(next)\n\t\tif err != nil {\n\t\t\tlogrus.Errorf(\"Analysis error for %v: %v\", next.Location, err)\n\t\t\tcontinue\n\t\t}\n\t\tlogrus.Debugf(\"Analyzed %v and found %v\", next.Location, deps)\n\t\tfor _, ref := range deps {\n\t\t\tif ref.HasPrefix(prefix) && len(ref) > len(prefix) {\n\t\t\t\tif s, ok := ref[len(prefix)].Value.(ast.String); ok {\n\t\t\t\t\tresultMap[string(s)] = struct{}{}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tvar result []ref\n\tfor x := range resultMap {\n\t\tresult = append(result, ref{Resource: x})\n\t}\n\n\tsort.Slice(result, func(i, j int) bool {\n\t\treturn result[i].Resource < result[j].Resource\n\t})\n\n\treturn result, nil, nil\n}\n"
  },
  {
    "path": "pkg/dynamicdata/dynamicdata_test.go",
    "content": "package dynamicdata\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"testing\"\n\n\t//lint:ignore SA1019 using OPA v0.x to ensure backwards compatible with pre-1.0 bundles\n\tsdktest \"github.com/open-policy-agent/opa/sdk/test\"\n\n\t\"github.com/open-policy-agent/opa/v1/logging\"\n)\n\nfunc TestAnalyzer(t *testing.T) {\n\n\tctx := context.Background()\n\n\ts := sdktest.MustNewServer(sdktest.RawBundles(true), sdktest.MockBundle(\"/bundles/bundle.tar.gz\", map[string]string{\n\t\t\".manifest\": `{\"roots\": [\"main\"]}`,\n\t\t\"main/main.rego\": `package main\n\t\timport rego.v1\n\t\tmain if { data.kubernetes.resources.pods[ns][_].metadata.labels.badlabel == \"badbadbad\"; r2 }\n\t\tr2 if { data.kubernetes.resources.namespaces[\"default\"].metadata.labels.foo == \"bar\" }`,\n\t}))\n\n\tdefer s.Stop()\n\n\tconfig := fmt.Appendf(nil, `{\n\t\tservices: {\n\t\t\ttest: {\n\t\t\t\turl: \"%v/bundles\"\n\t\t\t}\n\t\t},\n\t\tbundles: {\n\t\t\ttest: {\n\t\t\t\tservice: test,\n\t\t\t\tresource: bundle.tar.gz\n\t\t\t}\n\t\t}\n\t}`, s.URL())\n\n\ta, err := newAnalyzer(ctx, config, \"kubernetes/resources\", \"main/main\", logging.New())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tresult := <-a.C\n\tif len(result.Refs) != 2 || result.Refs[0].Resource != \"namespaces\" || result.Refs[1].Resource != \"pods\" {\n\t\tt.Fatalf(\"expected to identify pods reference but got: %v\", result)\n\t}\n\n\tif err := a.Stop(ctx); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n\nfunc TestAnalyzerNoDeps(t *testing.T) {\n\n\tctx := context.Background()\n\n\ts := sdktest.MustNewServer(sdktest.RawBundles(true), sdktest.MockBundle(\"/bundles/bundle.tar.gz\", map[string]string{\n\t\t\".manifest\": `{\"roots\": [\"main\"]}`,\n\t\t\"main/main.rego\": `package main\n\t\timport rego.v1\n\t\tmain if { true }`,\n\t}))\n\n\tdefer s.Stop()\n\n\tconfig := fmt.Appendf(nil, `{\n\t\tservices: {\n\t\t\ttest: {\n\t\t\t\turl: \"%v/bundles\"\n\t\t\t}\n\t\t},\n\t\tbundles: {\n\t\t\ttest: {\n\t\t\t\tservice: test,\n\t\t\t\tresource: bundle.tar.gz\n\t\t\t}\n\t\t}\n\t}`, s.URL())\n\n\ta, err := newAnalyzer(ctx, config, \"kubernetes/resources\", \"main/main\", logging.New())\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tresult := <-a.C\n\tif len(result.Refs) != 0 {\n\t\tt.Fatalf(\"expected not to identify any resources but got: %v\", result)\n\t}\n\n\tif err := a.Stop(ctx); err != nil {\n\t\tt.Fatal(err)\n\t}\n}\n"
  },
  {
    "path": "pkg/opa/opa.go",
    "content": "// Copyright 2017 The OPA Authors.  All rights reserved.\n// Use of this source code is governed by an Apache2\n// license that can be found in the LICENSE file.\n\npackage opa\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n)\n\n// Error contains the standard error fields returned by OPA.\ntype Error struct {\n\tCode    string          `json:\"code\"`\n\tMessage string          `json:\"message\"`\n\tErrors  json.RawMessage `json:\"errors,omitempty\"`\n}\n\nfunc (err *Error) Error() string {\n\treturn fmt.Sprintf(\"code %v: %v\", err.Code, err.Message)\n}\n\n// Undefined represents an undefined response from OPA.\ntype Undefined struct{}\n\nfunc (Undefined) Error() string {\n\treturn \"undefined\"\n}\n\n// IsUndefinedErr returns true if the err represents an undefined result from\n// OPA.\nfunc IsUndefinedErr(err error) bool {\n\t_, ok := err.(Undefined)\n\treturn ok\n}\n\n// Client defines the OPA client interface.\ntype Client interface {\n\tPolicies\n\tData\n}\n\n// Policies defines the policy management interface in OPA.\ntype Policies interface {\n\tInsertPolicy(id string, bs []byte) error\n\tDeletePolicy(id string) error\n}\n\n// Data defines the interface for pushing and querying data in OPA.\ntype Data interface {\n\tPrefix(path string) Data\n\tPatchData(path string, op string, value *interface{}) error\n\tPutData(path string, value interface{}) error\n\tPostData(path string, value interface{}) (json.RawMessage, error)\n}\n\n// New returns a new Client object.\nfunc New(url string, auth string) Client {\n\treturn &httpClient{strings.TrimRight(url, \"/\"), \"\", auth}\n}\n\ntype httpClient struct {\n\turl            string\n\tprefix         string\n\tauthentication string\n}\n\nfunc (c *httpClient) Prefix(path string) Data {\n\tcpy := *c\n\tcpy.prefix = joinPaths(\"/\", c.prefix, path)\n\treturn &cpy\n}\n\nfunc (c *httpClient) PatchData(path string, op string, value *interface{}) error {\n\tbuf, err := c.makePatch(path, op, value)\n\tif err != nil {\n\t\treturn err\n\t}\n\tresp, err := c.do(\"PATCH\", slashPath(\"data\"), buf)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn c.handleErrors(resp)\n}\n\nfunc (c *httpClient) PutData(path string, value interface{}) error {\n\tvar buf bytes.Buffer\n\tif err := json.NewEncoder(&buf).Encode(value); err != nil {\n\t\treturn err\n\t}\n\tabsPath := slashPath(\"data\", c.prefix, path)\n\tresp, err := c.do(\"PUT\", absPath, &buf)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn c.handleErrors(resp)\n}\n\nfunc (c *httpClient) PostData(path string, value interface{}) (json.RawMessage, error) {\n\tvar buf bytes.Buffer\n\tvar input struct {\n\t\tInput interface{} `json:\"input\"`\n\t}\n\tinput.Input = value\n\tif err := json.NewEncoder(&buf).Encode(input); err != nil {\n\t\treturn nil, err\n\t}\n\tabsPath := slashPath(\"data\", c.prefix, path)\n\tresp, err := c.do(\"POST\", absPath, &buf)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tvar result struct {\n\t\tResult json.RawMessage        `json:\"result\"`\n\t\tError  map[string]interface{} `json:\"error\"`\n\t}\n\tif resp.StatusCode != 200 {\n\t\treturn nil, c.handleErrors(resp)\n\t}\n\tif err := json.NewDecoder(resp.Body).Decode(&result); err != nil {\n\t\treturn nil, err\n\t}\n\tif result.Result == nil {\n\t\treturn nil, Undefined{}\n\t}\n\treturn result.Result, nil\n}\n\nfunc (c *httpClient) InsertPolicy(id string, bs []byte) error {\n\tbuf := bytes.NewBuffer(bs)\n\tpath := slashPath(\"policies\", id)\n\tresp, err := c.do(\"PUT\", path, buf)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn c.handleErrors(resp)\n}\n\nfunc (c *httpClient) DeletePolicy(id string) error {\n\tpath := slashPath(\"policies\", id)\n\tresp, err := c.do(\"DELETE\", path, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn c.handleErrors(resp)\n}\n\nfunc (c *httpClient) makePatch(path, op string, value *interface{}) (io.Reader, error) {\n\tpatch := []struct {\n\t\tPath  string       `json:\"path\"`\n\t\tOp    string       `json:\"op\"`\n\t\tValue *interface{} `json:\"value,omitempty\"`\n\t}{\n\t\t{\n\t\t\tPath:  slashPath(c.prefix, path),\n\t\t\tOp:    op,\n\t\t\tValue: value,\n\t\t},\n\t}\n\tvar buf bytes.Buffer\n\tif err := json.NewEncoder(&buf).Encode(patch); err != nil {\n\t\treturn nil, err\n\t}\n\treturn &buf, nil\n}\n\nfunc (c *httpClient) handleErrors(resp *http.Response) error {\n\tdefer resp.Body.Close()\n\tif resp.StatusCode >= 200 && resp.StatusCode < 300 {\n\t\treturn nil\n\t}\n\tvar err Error\n\tif err := json.NewDecoder(resp.Body).Decode(&err); err != nil {\n\t\treturn err\n\t}\n\treturn &err\n}\n\nfunc (c *httpClient) do(verb, path string, body io.Reader) (*http.Response, error) {\n\turl := c.url + path\n\treq, err := http.NewRequest(verb, url, body)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif c.authentication != \"\" {\n\t\treq.Header.Set(\"Authorization\", \"Bearer \"+c.authentication)\n\t}\n\n\treturn http.DefaultClient.Do(req)\n}\n\nfunc slashPath(paths ...string) string {\n\treturn makePath(\"/\", paths...)\n}\n\nfunc makePath(join string, paths ...string) string {\n\treturn join + joinPaths(join, paths...)\n}\n\nfunc joinPaths(join string, paths ...string) string {\n\tparts := []string{}\n\tfor _, path := range paths {\n\t\tpath = strings.Trim(path, join)\n\t\tif path != \"\" {\n\t\t\tparts = append(parts, path)\n\t\t}\n\t}\n\treturn strings.Join(parts, join)\n}\n"
  },
  {
    "path": "pkg/opa/opa_test.go",
    "content": "// Copyright 2018 The OPA Authors.  All rights reserved.\n// Use of this source code is governed by an Apache2\n// license that can be found in the LICENSE file.\n\npackage opa\n\nimport (\n\t\"encoding/json\"\n\t\"io\"\n\t\"reflect\"\n\t\"testing\"\n)\n\nfunc TestHTTPClientMakePatch(t *testing.T) {\n\n\ttests := []struct {\n\t\tprefix string\n\t\tpath   string\n\t\top     string\n\t\tvalue  string\n\t\twant   string\n\t}{\n\t\t{\n\t\t\tprefix: \"\",\n\t\t\tpath:   \"foo\",\n\t\t\top:     \"add\",\n\t\t\tvalue:  \"true\",\n\t\t\twant: `[{\n\t\t\t\t\"path\": \"/foo\",\n\t\t\t\t\"op\": \"add\",\n\t\t\t\t\"value\": true\n\t\t\t}]`,\n\t\t},\n\t\t{\n\t\t\tprefix: \"\",\n\t\t\tpath:   \"default/foo\",\n\t\t\top:     \"remove\",\n\t\t\tvalue:  \"\",\n\t\t\twant: `[{\n\t\t\t\t\"path\": \"/default/foo\",\n\t\t\t\t\"op\": \"remove\"\n\t\t\t}]`,\n\t\t},\n\t\t{\n\t\t\tprefix: \"type\",\n\t\t\tpath:   \"default/foo\",\n\t\t\top:     \"remove\",\n\t\t\tvalue:  \"\",\n\t\t\twant: `[{\n\t\t\t\t\"path\": \"/type/default/foo\",\n\t\t\t\t\"op\": \"remove\"\n\t\t\t}]`,\n\t\t},\n\t\t{\n\t\t\tprefix: \"/type1/subtypeA/\",\n\t\t\tpath:   \"default/foo\",\n\t\t\top:     \"remove\",\n\t\t\tvalue:  \"\",\n\t\t\twant: `[{\n\t\t\t\t\"path\": \"/type1/subtypeA/default/foo\",\n\t\t\t\t\"op\": \"remove\"\n\t\t\t}]`,\n\t\t},\n\t}\n\n\tfor _, tc := range tests {\n\n\t\tclient := &httpClient{\"URL\", tc.prefix, \"\"}\n\t\tvar value *interface{}\n\n\t\tif tc.value != \"\" {\n\t\t\tvar x interface{}\n\t\t\tif err := json.Unmarshal([]byte(tc.value), &x); err != nil {\n\t\t\t\tpanic(err)\n\t\t\t}\n\t\t\tvalue = &x\n\t\t}\n\n\t\tpatch := mustMakePatch(client, tc.path, tc.op, value)\n\n\t\tvar expected interface{}\n\t\tif err := json.Unmarshal([]byte(tc.want), &expected); err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\tif !reflect.DeepEqual(patch, expected) {\n\t\t\tt.Errorf(\"Expected %v but got: %v\", expected, patch)\n\t\t}\n\t}\n\n}\n\nfunc mustMakePatch(client *httpClient, path, op string, value *interface{}) interface{} {\n\n\tbuf, err := client.makePatch(path, op, value)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\treturn mustUnmarshalJSON(buf)\n}\n\nfunc mustUnmarshalJSON(r io.Reader) interface{} {\n\tvar x interface{}\n\terr := json.NewDecoder(r).Decode(&x)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\treturn x\n}\n"
  },
  {
    "path": "pkg/types/types.go",
    "content": "// Copyright 2017 The OPA Authors.  All rights reserved.\n// Use of this source code is governed by an Apache2\n// license that can be found in the LICENSE file.\n\n// Package types contains type information used by controllers.\npackage types\n\nimport \"strings\"\n\n// ResourceType describes a resource type in Kubernetes.\ntype ResourceType struct {\n\t// Namespaced indicates if this kind is namespaced.\n\tNamespaced bool\n\tResource   string\n\tGroup      string\n\tVersion    string\n}\n\nfunc (t ResourceType) String() string {\n\tparts := []string{}\n\tif t.Group != \"\" {\n\t\tparts = append(parts, t.Group)\n\t}\n\tif t.Version != \"\" {\n\t\tparts = append(parts, t.Version)\n\t}\n\tif t.Resource != \"\" {\n\t\tparts = append(parts, t.Resource)\n\t}\n\treturn strings.Join(parts, \"/\")\n}\n"
  },
  {
    "path": "pkg/version/version.go",
    "content": "// Copyright 2017 The OPA Authors.  All rights reserved.\n// Use of this source code is governed by an Apache2\n// license that can be found in the LICENSE file.\n\npackage version\n\n// Version information set by build process.\nvar (\n\tVersion = \"\"\n\tGit     = \"\"\n)\n"
  },
  {
    "path": "test/e2e/custom_config/1_bundle_loaded.hurl",
    "content": "GET https://localhost:8443/v1/data\nAuthorization: Bearer {{token}}\n\nHTTP 200\n[Asserts]\njsonpath \"$.result.test_helm_kubernetes_quickstart.*\" count == 3\n"
  },
  {
    "path": "test/e2e/custom_config/chainsaw-test.yaml",
    "content": "apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: custom-config\nspec:\n  namespace: default\n  steps:\n    - name: bundle is loaded\n      try:\n        - script:\n            content: |\n              hurl --variable token=$OPA_TOKEN --insecure --retry 15 --retry-interval 2s 1_bundle_loaded.hurl\n"
  },
  {
    "path": "test/e2e/custom_config/values.yaml",
    "content": "opa:\n  services:\n    controller:\n      url: 'https://www.openpolicyagent.org'\n  bundles:\n    quickstart:\n      service: controller\n      resource: /external-resources/bundles/helm-kubernetes-quickstart\n  default_decision: /helm_kubernetes_quickstart/main\n"
  },
  {
    "path": "test/e2e/custom_mgmt_token/1_policy_loaded.hurl",
    "content": "GET https://localhost:8443/v1/policies\nAuthorization: Bearer {{token}}\n\nHTTP 200\n[Asserts]\njsonpath \"$.result[?(@.id == 'default/policy-include/include.rego')]\" count >= 1\n\nGET https://localhost:8443/v1/data/example/include/allow\nAuthorization: Bearer {{token}}\n\nHTTP 200\n[Asserts]\njsonpath \"$.result\" == true\n"
  },
  {
    "path": "test/e2e/custom_mgmt_token/2_data_loaded.hurl",
    "content": "GET https://localhost:8443/v1/data/default\nAuthorization: Bearer {{token}}\n\nHTTP 200\n[Asserts]\njsonpath \"$.result.*\" count == 1\njsonpath \"$.result.data-include\" exists\n\nGET https://localhost:8443/v1/data/default/data-include\nAuthorization: Bearer {{token}}\n\nHTTP 200\n[Asserts]\njsonpath \"$.result['include.json'].inKey\" == \"inValue\"\n"
  },
  {
    "path": "test/e2e/custom_mgmt_token/chainsaw-test.yaml",
    "content": "apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: custom-mgmt-token\nspec:\n  namespace: default\n  steps:\n    - name: apply fixtures\n      try:\n        - apply:\n            file: ../fixture.yaml\n      cleanup:\n        - delete:\n            file: ../fixture.yaml\n\n    - name: policy is loaded and evaluated correctly\n      try:\n        - script:\n            content: |\n              hurl --variable token=$OPA_TOKEN --insecure --retry 3 1_policy_loaded.hurl\n\n    - name: data is loaded correctly\n      try:\n        - script:\n            content: |\n              hurl --variable token=$OPA_TOKEN --insecure 2_data_loaded.hurl\n\n    - name: configmap status annotations are ok\n      try:\n        - assert:\n            resource:\n              apiVersion: v1\n              kind: ConfigMap\n              metadata:\n                labels:\n                  openpolicyagent.org/policy: rego\n                annotations:\n                  openpolicyagent.org/kube-mgmt-status: '{\"status\":\"ok\"}'\n        - assert:\n            resource:\n              apiVersion: v1\n              kind: ConfigMap\n              metadata:\n                labels:\n                  openpolicyagent.org/data: opa\n                annotations:\n                  openpolicyagent.org/kube-mgmt-status: '{\"status\":\"ok\"}'\n"
  },
  {
    "path": "test/e2e/custom_mgmt_token/values.yaml",
    "content": "e2eMgmtTokenSecret: true\nopa:\n  replicas: 2\nauthz:\n  enabled: true\n  mgmtToken:\n    secretName: mgmt-token-secret"
  },
  {
    "path": "test/e2e/default/1_initial_state.hurl",
    "content": "GET https://localhost:8443/v1/data\nAuthorization: Bearer {{token}}\n\nHTTP 200\n[Asserts]\njsonpath \"$.result.*\" count == 0\n"
  },
  {
    "path": "test/e2e/default/2_policy_loaded.hurl",
    "content": "GET https://localhost:8443/v1/policies\nAuthorization: Bearer {{token}}\n\nHTTP 200\n[Asserts]\njsonpath \"$.result[?(@.id == 'default/policy-include/include.rego')]\" count >= 1\n\nGET https://localhost:8443/v1/data/example/include/allow\nAuthorization: Bearer {{token}}\n\nHTTP 200\n[Asserts]\njsonpath \"$.result\" == true\n"
  },
  {
    "path": "test/e2e/default/3_data_loaded.hurl",
    "content": "GET https://localhost:8443/v1/data/default\nAuthorization: Bearer {{token}}\n\nHTTP 200\n[Asserts]\njsonpath \"$.result.*\" count == 1\njsonpath \"$.result.data-include\" exists\n\nGET https://localhost:8443/v1/data/default/data-include\nAuthorization: Bearer {{token}}\n\nHTTP 200\n[Asserts]\njsonpath \"$.result['include.json'].inKey\" == \"inValue\"\n"
  },
  {
    "path": "test/e2e/default/chainsaw-test.yaml",
    "content": "apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: default\nspec:\n  namespace: default\n  steps:\n    - name: initial state is empty\n      try:\n        - script:\n            content: |\n              hurl --variable token=$OPA_TOKEN --insecure --retry 3 1_initial_state.hurl\n\n    - name: apply fixtures\n      try:\n        - apply:\n            file: ../fixture.yaml\n      cleanup:\n        - delete:\n            file: ../fixture.yaml\n\n    - name: policy is loaded and evaluated correctly\n      try:\n        - script:\n            content: |\n              hurl --variable token=$OPA_TOKEN --insecure --retry 3 2_policy_loaded.hurl\n\n    - name: data is loaded correctly\n      try:\n        - script:\n            content: |\n              hurl --variable token=$OPA_TOKEN --insecure 3_data_loaded.hurl\n\n    - name: configmap status annotations are ok\n      try:\n        - assert:\n            resource:\n              apiVersion: v1\n              kind: ConfigMap\n              metadata:\n                labels:\n                  openpolicyagent.org/policy: rego\n                annotations:\n                  openpolicyagent.org/kube-mgmt-status: '{\"status\":\"ok\"}'\n        - assert:\n            resource:\n              apiVersion: v1\n              kind: ConfigMap\n              metadata:\n                labels:\n                  openpolicyagent.org/data: opa\n                annotations:\n                  openpolicyagent.org/kube-mgmt-status: '{\"status\":\"ok\"}'\n"
  },
  {
    "path": "test/e2e/default/values.yaml",
    "content": ""
  },
  {
    "path": "test/e2e/fixture-labels.yaml",
    "content": "---\nkind: ConfigMap\nmetadata:\n  name: policy-include\n  labels:\n    kube-mgmt/e2e: \"true\"\n    qweqwe/policy: \"111\"\napiVersion: v1\ndata:\n  include.rego: |\n    package example.include\n    allow := true\n---\nkind: ConfigMap\nmetadata:\n  name: policy-exclude\n  labels:\n    kube-mgmt/e2e: \"true\"\n    openpolicyagent.org/policy: rego\napiVersion: v1\ndata:\n  exclude.rego: |\n    package example.exclude\n    allow := true\n---\nkind: ConfigMap\nmetadata:\n  name: data-include\n  labels:\n    kube-mgmt/e2e: \"true\"\n    asdasd/data: \"222\"\napiVersion: v1\ndata:\n  include.json: |\n    {\"inKey\": \"inValue\"}\n---\nkind: ConfigMap\nmetadata:\n  name: data-exclude\n  labels:\n    kube-mgmt/e2e: \"true\"\n    openpolicyagent.org/data: opa\napiVersion: v1\ndata:\n  exclude.json: |\n    {\"exKey\": \"exValue\"}\n---\nkind: ConfigMap\nmetadata:\n  name: data-labeless\n  labels:\n    kube-mgmt/e2e: \"true\"\napiVersion: v1\ndata:\n  labeless.json: |\n    {\"lbKey\": \"lbValue\"}\n---\nkind: ConfigMap\nmetadata:\n  name: policy-labeless\n  labels:\n    kube-mgmt/e2e: \"true\"\napiVersion: v1\ndata:\n  labeless.rego: |\n    package example.labeless\n    allow := true\n"
  },
  {
    "path": "test/e2e/fixture-multi.yaml",
    "content": "---\nkind: ConfigMap\nmetadata:\n  name: multi-file-policy\n  labels:\n    kube-mgmt/e2e: \"true\"\n    openpolicyagent.org/policy: rego\napiVersion: v1\ndata:\n  a.rego: |\n    package my_pkg\n    import rego.v1\n    import data.my_pkg.functions.my_func\n    default my_rule := false\n    my_rule if {\n      my_func(input.hello)\n    }\n  b.rego: |\n    package my_pkg.functions\n    my_func(str) := startswith(\"world\", str)\n---\nkind: ConfigMap\nmetadata:\n  name: multi-file-fail-policy\n  labels:\n    kube-mgmt/e2e: \"true\"\n    openpolicyagent.org/policy: rego\napiVersion: v1\ndata:\n  f.rego: |\n    package my_pkg_fail\n    import rego.v1\n    import data.my_pkg_fail.functions.my_func\n    default my_rule := false\n    my_rule if {\n      my_func(input.hello)\n    }\n"
  },
  {
    "path": "test/e2e/fixture-replication.yaml",
    "content": "---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: ignore-me\n---\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: dont-ignore-me\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: dont-ignore-me\n  namespace: dont-ignore-me\n  labels:\n    kube-mgmt/e2e: \"true\"\nspec:\n  ports:\n    - name: http\n      port: 8080\n---\napiVersion: v1\nkind: Service\nmetadata:\n  name: ignore-me\n  namespace: ignore-me\n  labels:\n    kube-mgmt/e2e: \"true\"\nspec:\n  ports:\n    - name: http\n      port: 8080\n"
  },
  {
    "path": "test/e2e/fixture.yaml",
    "content": "---\nkind: ConfigMap\nmetadata:\n  name: policy-include\n  labels:\n    kube-mgmt/e2e: \"true\"\n    openpolicyagent.org/policy: rego\napiVersion: v1\ndata:\n  include.rego: |\n    package example.include\n    allow := true\n---\nkind: ConfigMap\nmetadata:\n  name: policy-exclude\n  labels:\n    kube-mgmt/e2e: \"true\"\n    openpolicyagent.org/policy: qwerty\napiVersion: v1\ndata:\n  exclude.rego: |\n    package example.exclude\n    allow := true\n---\nkind: ConfigMap\nmetadata:\n  name: data-include\n  labels:\n    kube-mgmt/e2e: \"true\"\n    openpolicyagent.org/data: opa\napiVersion: v1\ndata:\n  include.json: |\n    {\"inKey\": \"inValue\"}\n---\nkind: ConfigMap\nmetadata:\n  name: data-exclude\n  labels:\n    kube-mgmt/e2e: \"true\"\n    openpolicyagent.org/data: qwerty\napiVersion: v1\ndata:\n  exclude.json: |\n    {\"exKey\": \"exValue\"}\n---\nkind: ConfigMap\nmetadata:\n  name: data-labeless\n  labels:\n    kube-mgmt/e2e: \"true\"\napiVersion: v1\ndata:\n  labeless.json: |\n    {\"lbKey\": \"lbValue\"}\n---\nkind: ConfigMap\nmetadata:\n  name: policy-labeless\n  labels:\n    kube-mgmt/e2e: \"true\"\napiVersion: v1\ndata:\n  labeless.rego: |\n    package example.labeless\n    allow := true\n"
  },
  {
    "path": "test/e2e/labels/1_initial_state.hurl",
    "content": "GET http://localhost:8080/v1/data\nHTTP 200\n[Asserts]\njsonpath \"$.result.*\" count == 0\n"
  },
  {
    "path": "test/e2e/labels/2_policy_loaded.hurl",
    "content": "GET http://localhost:8080/v1/policies\nHTTP 200\n[Asserts]\njsonpath \"$.result[?(@.id == 'default/policy-include/include.rego')]\" count >= 1\n\nGET http://localhost:8080/v1/data/example/include/allow\nHTTP 200\n[Asserts]\njsonpath \"$.result\" == true\n"
  },
  {
    "path": "test/e2e/labels/3_data_loaded.hurl",
    "content": "GET http://localhost:8080/v1/data/default\nHTTP 200\n[Asserts]\njsonpath \"$.result.*\" count == 1\njsonpath \"$.result.data-include\" exists\n\nGET http://localhost:8080/v1/data/default/data-include\nHTTP 200\n[Asserts]\njsonpath \"$.result['include.json'].inKey\" == \"inValue\"\n"
  },
  {
    "path": "test/e2e/labels/chainsaw-test.yaml",
    "content": "apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: labels\nspec:\n  namespace: default\n  steps:\n    - name: initial state is empty\n      try:\n        - script:\n            content: |\n              hurl --retry 3 1_initial_state.hurl\n\n    - name: apply fixtures\n      try:\n        - apply:\n            file: ../fixture-labels.yaml\n      cleanup:\n        - delete:\n            file: ../fixture-labels.yaml\n\n    - name: policy is loaded and evaluated correctly\n      try:\n        - script:\n            content: |\n              hurl --retry 3 2_policy_loaded.hurl\n\n    - name: data is loaded correctly\n      try:\n        - script:\n            content: |\n              hurl 3_data_loaded.hurl\n\n    - name: configmap status annotations are ok\n      try:\n        - assert:\n            resource:\n              apiVersion: v1\n              kind: ConfigMap\n              metadata:\n                labels:\n                  qweqwe/policy: \"111\"\n                annotations:\n                  openpolicyagent.org/kube-mgmt-status: '{\"status\":\"ok\"}'\n        - assert:\n            resource:\n              apiVersion: v1\n              kind: ConfigMap\n              metadata:\n                labels:\n                  asdasd/data: \"222\"\n                annotations:\n                  openpolicyagent.org/kube-mgmt-status: '{\"status\":\"ok\"}'\n"
  },
  {
    "path": "test/e2e/labels/values.yaml",
    "content": "useHttps: false\n\nopa: null\n\nauthz:\n  enabled: false\n\nmgmt:\n  startupProbe:\n    httpGet:\n      scheme: HTTP\n  extraArgs:\n    - \"--policy-label=qweqwe/policy\"\n    - \"--policy-value=111\"\n    - \"--data-label=asdasd/data\"\n    - \"--data-value=222\"\n    - \"--log-level=debug\"\n"
  },
  {
    "path": "test/e2e/multi/1_initial_state.hurl",
    "content": "GET http://localhost:8080/v1/data/my_pkg/my_rule\nHTTP 200\n[Asserts]\njsonpath \"$.result\" not exists\n"
  },
  {
    "path": "test/e2e/multi/2_policies_loaded.hurl",
    "content": "GET http://localhost:8080/v1/policies\nHTTP 200\n[Asserts]\njsonpath \"$.result\" count == 2\njsonpath \"$.result[?(@.id == 'default/multi-file-policy/a.rego')]\" count >= 1\njsonpath \"$.result[?(@.id == 'default/multi-file-policy/b.rego')]\" count >= 1\n\nPOST http://localhost:8080/v1/data/my_pkg/my_rule\nContent-Type: application/json\n{\"input\": {\"hello\": \"world\"}}\n\nHTTP 200\n[Asserts]\njsonpath \"$.result\" == true\n\nPOST http://localhost:8080/v1/data/my_pkg/my_rule\nContent-Type: application/json\n{\"input\": {\"hello\": \"incorrect\"}}\n\nHTTP 200\n[Asserts]\njsonpath \"$.result\" == false\n"
  },
  {
    "path": "test/e2e/multi/3_policy_unloaded.hurl",
    "content": "GET http://localhost:8080/v1/data/my_pkg/my_rule\nHTTP 200\n[Asserts]\njsonpath \"$.result\" not exists\n"
  },
  {
    "path": "test/e2e/multi/4_policies_reloaded.hurl",
    "content": "GET http://localhost:8080/v1/policies\nHTTP 200\n[Asserts]\njsonpath \"$.result\" count == 2\njsonpath \"$.result[?(@.id == 'default/multi-file-policy/a.rego')]\" count >= 1\njsonpath \"$.result[?(@.id == 'default/multi-file-policy/b.rego')]\" count >= 1\n\nPOST http://localhost:8080/v1/data/my_pkg/my_rule\nContent-Type: application/json\n{\"input\": {\"hello\": \"world\"}}\n\nHTTP 200\n[Asserts]\njsonpath \"$.result\" == true\n\nPOST http://localhost:8080/v1/data/my_pkg/my_rule\nContent-Type: application/json\n{\"input\": {\"hello\": \"incorrect\"}}\n\nHTTP 200\n[Asserts]\njsonpath \"$.result\" == false\n"
  },
  {
    "path": "test/e2e/multi/chainsaw-test.yaml",
    "content": "apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: multi\nspec:\n  namespace: default\n  steps:\n    - name: initial state is empty\n      try:\n        - script:\n            content: |\n              hurl --retry 3 1_initial_state.hurl\n\n    - name: apply fixtures\n      try:\n        - apply:\n            file: ../fixture-multi.yaml\n      cleanup:\n        - delete:\n            file: ../fixture-multi.yaml\n\n    - name: policies are loaded and evaluated correctly\n      try:\n        - script:\n            content: |\n              hurl --retry 3 2_policies_loaded.hurl\n\n    - name: configmap status annotations are ok\n      try:\n        - assert:\n            resource:\n              apiVersion: v1\n              kind: ConfigMap\n              metadata:\n                name: multi-file-policy\n                annotations:\n                  openpolicyagent.org/kube-mgmt-status: '{\"status\":\"ok\"}'\n                  openpolicyagent.org/kube-mgmt-retries: \"0\"\n        - script:\n            content: |\n              kubectl get cm multi-file-fail-policy -o json \\\n                | yq -e '.metadata.annotations[\"openpolicyagent.org/kube-mgmt-status\"] | from_json | .status == \"error\"'\n              kubectl get cm multi-file-fail-policy -o json \\\n                | yq -e '.metadata.annotations[\"openpolicyagent.org/kube-mgmt-retries\"] == \"0\"'\n\n    - name: remove policy label\n      try:\n        - script:\n            content: |\n              kubectl patch cm multi-file-policy --type=merge \\\n                -p '{\"metadata\":{\"labels\":{\"openpolicyagent.org/policy\":\"\"}}}'\n\n    - name: policy is unloaded\n      try:\n        - script:\n            content: |\n              hurl --retry 3 3_policy_unloaded.hurl\n\n    - name: re-add policy label\n      try:\n        - script:\n            content: |\n              kubectl label --overwrite cm multi-file-policy openpolicyagent.org/policy=rego\n\n    - name: policies are reloaded and evaluated correctly\n      try:\n        - script:\n            content: |\n              hurl --retry 3 4_policies_reloaded.hurl\n"
  },
  {
    "path": "test/e2e/multi/values.yaml",
    "content": "useHttps: false\n\nopa: null\n\nauthz:\n  enabled: false\n\nmgmt:\n  startupProbe:\n    httpGet:\n      scheme: HTTP\n  extraArgs:\n    - \"--log-level=debug\"\n"
  },
  {
    "path": "test/e2e/replicate/1_replication.hurl",
    "content": "GET http://localhost:8080/v1/data/kubernetes/services/ignore-me\nHTTP 200\n[Asserts]\njsonpath \"$.result\" not exists\n\nGET http://localhost:8080/v1/data/kubernetes/services/dont-ignore-me\nHTTP 200\n[Asserts]\njsonpath \"$.result.*\" count == 1\njsonpath \"$.result.dont-ignore-me\" exists\n\nGET http://localhost:8080/v1/data/kubernetes/services/default\nHTTP 200\n[Asserts]\njsonpath \"$.result.*\" count == 2\n"
  },
  {
    "path": "test/e2e/replicate/chainsaw-test.yaml",
    "content": "apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: replicate\nspec:\n  steps:\n    - name: apply fixtures\n      try:\n        - apply:\n            file: ../fixture-replication.yaml\n      cleanup:\n        - delete:\n            file: ../fixture-replication.yaml\n\n    - name: replication is correct\n      try:\n        - script:\n            content: |\n              hurl --retry 3 1_replication.hurl\n"
  },
  {
    "path": "test/e2e/replicate/values.yaml",
    "content": "useHttps: false\n\nopa: null\n\nauthz:\n  enabled: false\n\nmgmt:\n  data:\n    enabled: false\n  policies:\n    enabled: false\n  startupProbe:\n    httpGet:\n      scheme: HTTP\n  replicate:\n    ignoreNs:\n      - \"ignore-me\"\n    namespace:\n      - v1/services\n  extraArgs:\n    - \"--log-level=debug\"\n\nrbac:\n  extraRules:\n    - apiGroups: [\"\"]\n      resources: [\"services\"]\n      verbs: [\"*\"]\n"
  },
  {
    "path": "test/e2e/replicate_auto/.gitignore",
    "content": "bundle.tar.gz"
  },
  {
    "path": "test/e2e/replicate_auto/1_replication.hurl",
    "content": "GET http://localhost:8080/v1/data/kubernetes/services/ignore-me\nHTTP 200\n[Asserts]\njsonpath \"$.result\" not exists\n\nGET http://localhost:8080/v1/data/kubernetes/services/dont-ignore-me\nHTTP 200\n[Asserts]\njsonpath \"$.result.*\" count == 1\njsonpath \"$.result.dont-ignore-me\" exists\n\nGET http://localhost:8080/v1/data/kubernetes/services/default\nHTTP 200\n[Asserts]\njsonpath \"$.result.*\" count == 2\n"
  },
  {
    "path": "test/e2e/replicate_auto/bundle/.manifest",
    "content": "{\n    \"roots\": [\"main\"]\n}"
  },
  {
    "path": "test/e2e/replicate_auto/bundle/main.rego",
    "content": "package main\n\nimport rego.v1\n\nmain if {\n    some ns, name\n    data.kubernetes.services[ns][name].metadata.labels == \"foo\"\n}"
  },
  {
    "path": "test/e2e/replicate_auto/chainsaw-test.yaml",
    "content": "apiVersion: chainsaw.kyverno.io/v1alpha1\nkind: Test\nmetadata:\n  name: replicate-auto\nspec:\n  steps:\n    - name: apply fixtures\n      try:\n        - apply:\n            file: ../fixture-replication.yaml\n      cleanup:\n        - delete:\n            file: ../fixture-replication.yaml\n\n    - name: replication is correct\n      try:\n        - script:\n            content: |\n              hurl --retry 3 1_replication.hurl\n"
  },
  {
    "path": "test/e2e/replicate_auto/values.yaml",
    "content": "useHttps: false\n\nopa: {\n  bundles: {\n    test: {\n      resource: file:///bundle/bundle.tar.gz\n    }\n  }\n}\n\nauthz:\n  enabled: false\n\nmgmt:\n  data:\n    enabled: false\n  policies:\n    enabled: false\n  startupProbe:\n    httpGet:\n      scheme: HTTP\n  replicate:\n    ignoreNs:\n      - \"ignore-me\"\n    auto: true\n  extraArgs:\n    - \"--log-level=debug\"\n\nrbac:\n  extraRules:\n    - apiGroups: [\"\"]\n      resources: [\"services\"]\n      verbs: [\"*\"]\n\nextraVolumes:\n  - name: bundle-vol\n    configMap:\n      name: bundle\n\nextraVolumeMounts:\n  - name: bundle-vol\n    mountPath: /bundle"
  },
  {
    "path": "test/lint/images.yaml",
    "content": "suite: lint image and mgmt.image\ntemplates:\n  - fake.yaml\ntests:\n  - it: image is null\n    set:\n      image: null\n    asserts:\n      - failedTemplate:\n          errorMessage: |\n            values don't meet the specifications of the schema(s) in the following chart(s):\n            opa-kube-mgmt:\n            - at '': missing property 'image'\n\n  - it: image.repository not string\n    set:\n      image:\n        repository: 5\n    asserts:\n      - failedTemplate:\n          errorMessage: |\n            values don't meet the specifications of the schema(s) in the following chart(s):\n            opa-kube-mgmt:\n            - at '/image/repository': got number, want string\n\n  - it: image.tag not string\n    set:\n      image:\n        tag: 5\n    asserts:\n      - failedTemplate:\n          errorMessage: |\n            values don't meet the specifications of the schema(s) in the following chart(s):\n            opa-kube-mgmt:\n            - at '/image/tag': got number, want string\n\n  - it: mgmt.image is null\n    set:\n      mgmt:\n        image: null\n    asserts:\n      - failedTemplate:\n          errorMessage: |\n            values don't meet the specifications of the schema(s) in the following chart(s):\n            opa-kube-mgmt:\n            - at '/mgmt': missing property 'image'\n\n  - it: mgmt.image.repository not string\n    set:\n      mgmt:\n        image:\n          repository: 5\n    asserts:\n      - failedTemplate:\n          errorMessage: |\n            values don't meet the specifications of the schema(s) in the following chart(s):\n            opa-kube-mgmt:\n            - at '/mgmt/image/repository': got number, want string\n\n  - it: mgmt.image.tag not string\n    set:\n      mgmt:\n        image:\n          tag: 5\n    asserts:\n      - failedTemplate:\n          errorMessage: |\n            values don't meet the specifications of the schema(s) in the following chart(s):\n            opa-kube-mgmt:\n            - at '/mgmt/image/tag': got number, want string\n"
  },
  {
    "path": "test/lint/sa.yaml",
    "content": "suite: lint serviceaccount\ntemplates:\n  - fake.yaml\ntests:\n  - it: annotations not string\n    set:\n      serviceAccount:\n        annotations:\n          foo: 1\n    asserts:\n      - failedTemplate:\n          errorPattern: \"got number, want string\"\n"
  },
  {
    "path": "test/lint/service.yaml",
    "content": "suite: lint service\ntemplates:\n  - service.yaml\ntests:\n  - it: fails when service annotation is boolean\n    set:\n      service:\n        annotations:\n          bar: true\n    asserts:\n      - failedTemplate:\n          errorPattern: \"got boolean, want string\"\n\n  - it: fails when service annotation is array\n    set:\n      service:\n        annotations:\n          baz: [\"invalid\"]\n    asserts:\n      - failedTemplate:\n          errorPattern: \"got array, want string\"\n\n  - it: fails when service annotation is object\n    set:\n      service:\n        annotations:\n          foo:\n            bar: baz\n    asserts:\n      - failedTemplate:\n          errorPattern: \"got object, want string\"\n\n  - it: trafficDistribution invalid value\n    set:\n      service:\n        trafficDistribution: \"InvalidValue\"\n    asserts:\n      - failedTemplate:\n          errorMessage: |\n            values don't meet the specifications of the schema(s) in the following chart(s):\n            opa-kube-mgmt:\n            - at '/service/trafficDistribution': value must be one of 'PreferClose', 'PreferSameNode', 'PreferSameZone', <nil>\n"
  },
  {
    "path": "test/lint/tsc.yaml",
    "content": "suite: lint topologySpreadConstraints\ntemplates:\n  - deployment.yaml\ntests:\n  - it: fails when maxSkew is missing\n    set:\n      topologySpreadConstraints:\n        - topologyKey: \"kubernetes.io/hostname\"\n          whenUnsatisfiable: \"DoNotSchedule\"\n    asserts:\n      - failedTemplate: {}\n\n  - it: fails when maxSkew is null\n    set:\n      topologySpreadConstraints:\n        - maxSkew: null\n          topologyKey: \"kubernetes.io/hostname\"\n          whenUnsatisfiable: \"DoNotSchedule\"\n    asserts:\n      - failedTemplate: {}\n\n  - it: fails when topologyKey is missing\n    set:\n      topologySpreadConstraints:\n        - maxSkew: 1\n          whenUnsatisfiable: \"DoNotSchedule\"\n    asserts:\n      - failedTemplate: {}\n\n  - it: fails when whenUnsatisfiable is missing\n    set:\n      topologySpreadConstraints:\n        - maxSkew: 1\n          topologyKey: \"kubernetes.io/hostname\"\n    asserts:\n      - failedTemplate: {}\n\n  - it: fails when maxSkew is not an integer\n    set:\n      topologySpreadConstraints:\n        - maxSkew: \"one\"\n          topologyKey: \"kubernetes.io/hostname\"\n          whenUnsatisfiable: \"DoNotSchedule\"\n    asserts:\n      - failedTemplate: {}\n\n  - it: fails when topologyKey is empty string\n    set:\n      topologySpreadConstraints:\n        - maxSkew: 1\n          topologyKey: \"\"\n          whenUnsatisfiable: \"DoNotSchedule\"\n    asserts:\n      - failedTemplate: {}\n\n  - it: fails when whenUnsatisfiable has invalid value\n    set:\n      topologySpreadConstraints:\n        - maxSkew: 1\n          topologyKey: \"kubernetes.io/hostname\"\n          whenUnsatisfiable: \"InvalidOption\"\n    asserts:\n      - failedTemplate: {}\n\n  - it: renders with empty topologySpreadConstraints array\n    set:\n      topologySpreadConstraints: []\n    asserts:\n      - isKind:\n          of: Deployment\n      - isEmpty:\n          path: spec.template.spec.topologySpreadConstraints\n\n  - it: renders without topologySpreadConstraints when not set\n    asserts:\n      - isKind:\n          of: Deployment\n      - isEmpty:\n          path: spec.template.spec.topologySpreadConstraints\n"
  },
  {
    "path": "test/unit/health.yaml",
    "content": "suite: test health probes\ntemplates:\n  - deployment.yaml\ntests:\n  - it: should have only liveness and readiness for OPA\n    asserts:\n      - notExists:\n          path: spec.template.spec.containers[0].startupProbe\n      - exists:\n          path: spec.template.spec.containers[0].readinessProbe\n      - exists:\n          path: spec.template.spec.containers[0].livenessProbe\n      - equal:\n          path: spec.template.spec.containers[0].readinessProbe.httpGet.scheme\n          value: HTTPS\n      - equal:\n          path: spec.template.spec.containers[0].livenessProbe.httpGet.scheme\n          value: HTTPS\n  - it: should override scheme for liveness and readiness for OPA\n    set:\n      useHttps: false\n    asserts:\n      - equal:\n          path: spec.template.spec.containers[0].readinessProbe.httpGet.scheme\n          value: HTTP\n      - equal:\n          path: spec.template.spec.containers[0].livenessProbe.httpGet.scheme\n          value: HTTP\n  - it: should have only startup for kube-mgmt\n    asserts:\n      - exists:\n          path: spec.template.spec.containers[1].startupProbe\n      - notExists:\n          path: spec.template.spec.containers[1].readinessProbe\n      - notExists:\n          path: spec.template.spec.containers[1].livenessProbe\n      - equal:\n          path: spec.template.spec.containers[1].startupProbe.httpGet.scheme\n          value: HTTPS\n  - it: should override startup for kube-mgmt\n    set:\n      mgmt:\n        startupProbe:\n          failureThreshold: 11\n          timeoutSeconds: 22\n    asserts:\n      - exists:\n          path: spec.template.spec.containers[1].startupProbe\n      - notExists:\n          path: spec.template.spec.containers[1].readinessProbe\n      - notExists:\n          path: spec.template.spec.containers[1].livenessProbe\n      - equal:\n          path: spec.template.spec.containers[1].startupProbe.failureThreshold\n          value: 11\n      - equal:\n          path: spec.template.spec.containers[1].startupProbe.timeoutSeconds\n          value: 22\n"
  },
  {
    "path": "test/unit/kube-mgmt_args.yaml",
    "content": "suite: test kube-mgmt container args\ntemplates:\n  - deployment.yaml\ntests:\n  - it: should have default args\n    asserts:\n      - contains:\n          path: spec.template.spec.containers[1].args\n          content: \"--enable-data=true\"\n      - contains:\n          path: spec.template.spec.containers[1].args\n          content: \"--enable-data=true\"\n      - contains:\n          path: spec.template.spec.containers[1].args\n          content: \"--namespaces=NAMESPACE\"\n      - contains:\n          path: spec.template.spec.containers[1].args\n          content: \"--replicate-ignore-namespaces=\"\n  - it: should override args\n    set:\n      mgmt:\n        namespaces: [\"111\", \"222\"]\n        replicate:\n          ignoreNs: [\"qwe\", \"asd\"]\n    asserts:\n      - contains:\n          path: spec.template.spec.containers[1].args\n          content: \"--replicate-ignore-namespaces=qwe,asd\"\n      - contains:\n          path: spec.template.spec.containers[1].args\n          content: \"--namespaces=111,222\"\n  - it: should override all namespaces 1\n    set:\n      mgmt:\n        namespaces: [\"*\"]\n    asserts:\n      - contains:\n          path: spec.template.spec.containers[1].args\n          content: \"--namespaces=*\"\n  - it: should override all namespaces 2\n    set:\n      mgmt:\n        namespaces: \"*\"\n    asserts:\n      - contains:\n          path: spec.template.spec.containers[1].args\n          content: \"--namespaces=*\"\n  - it: should add extraVolumes if authz is disabled & no bootstrapPolicies are provided\n    set:\n      useHttps: false\n      authz:\n        enabled: false\n      extraVolumes:\n        - name: example-app-auth-config\n          secret:\n            secretName: example-app-auth-config\n    asserts:\n      - contains:\n          path: spec.template.spec.volumes\n          content: \n            name: example-app-auth-config\n            secret:\n              secretName: example-app-auth-config\n"
  },
  {
    "path": "test/unit/rbac_cm.yaml",
    "content": "suite: test configmap rbac\ntemplates:\n  - rbac-mgmt.yaml\ntests:\n  - it: should create current namespace role by default\n    asserts:\n      - hasDocuments:\n          count: 2\n      - containsDocument:\n          kind: Role\n          apiVersion: rbac.authorization.k8s.io/v1\n          namespace: NAMESPACE\n          any: true\n      - containsDocument:\n          kind: RoleBinding\n          apiVersion: rbac.authorization.k8s.io/v1\n          namespace: NAMESPACE\n          any: true\n\n  - it: should create namespace roles when namespaces configured\n    set:\n      mgmt:\n        namespaces: [\"qwe\", \"asd\"]\n    asserts:\n      - hasDocuments:\n          count: 4\n      - containsDocument:\n          kind: Role\n          apiVersion: rbac.authorization.k8s.io/v1\n          namespace: \"qwe\"\n          any: true\n      - containsDocument:\n          kind: Role\n          apiVersion: rbac.authorization.k8s.io/v1\n          namespace: \"asd\"\n          any: true\n      - containsDocument:\n          kind: RoleBinding\n          apiVersion: rbac.authorization.k8s.io/v1\n          namespace: \"qwe\"\n          any: true\n      - containsDocument:\n          kind: RoleBinding\n          apiVersion: rbac.authorization.k8s.io/v1\n          namespace: \"asd\"\n          any: true\n\n  - it: should create cluster role if namespace is asterisk\n    set:\n      mgmt:\n        namespaces: \"*\"\n    asserts:\n      - hasDocuments:\n          count: 2\n      - containsDocument:\n          kind: ClusterRole\n          apiVersion: rbac.authorization.k8s.io/v1\n          any: true\n      - containsDocument:\n          kind: ClusterRoleBinding\n          apiVersion: rbac.authorization.k8s.io/v1\n          any: true\n  - it: should create cluster role if namespace is single item array with asterisk\n    set:\n      mgmt:\n        namespaces: [\"*\"]\n    asserts:\n      - hasDocuments:\n          count: 2\n      - containsDocument:\n          kind: ClusterRole\n          apiVersion: rbac.authorization.k8s.io/v1\n          any: true\n      - containsDocument:\n          kind: ClusterRoleBinding\n          apiVersion: rbac.authorization.k8s.io/v1\n          any: true\n\n  - it: should not create roles if rbac disabled\n    set:\n      rbac:\n        create: false\n    asserts:\n      - hasDocuments:\n          count: 0\n  - it: should not create roles if mgmt disabled\n    set:\n      mgmt:\n        enabled: false\n    asserts:\n      - hasDocuments:\n          count: 0\n"
  },
  {
    "path": "test/unit/rbac_replicate.yaml",
    "content": "suite: test replicate rbac\ntemplates:\n  - rbac-mgmt-replicate.yaml\ntests:\n  - it: should not create cluster role by default\n    asserts:\n      - hasDocuments:\n          count: 0\n  - it: should create cluster role if has namespace\n    set:\n      mgmt:\n        replicate:\n          namespace: [\"qwe\"]\n    asserts:\n      - hasDocuments:\n          count: 2\n      - containsDocument:\n          kind: ClusterRole\n          apiVersion: rbac.authorization.k8s.io/v1\n          any: true\n      - containsDocument:\n          kind: ClusterRoleBinding\n          apiVersion: rbac.authorization.k8s.io/v1\n          any: true\n  - it: should create cluster role if has cluster\n    set:\n      mgmt:\n        replicate:\n          cluster: [\"qwe\"]\n    asserts:\n      - hasDocuments:\n          count: 2\n      - containsDocument:\n          kind: ClusterRole\n          apiVersion: rbac.authorization.k8s.io/v1\n          any: true\n      - containsDocument:\n          kind: ClusterRoleBinding\n          apiVersion: rbac.authorization.k8s.io/v1\n          any: true\n  - it: should not create cluster role if rbac disabled\n    set:\n      rbac:\n        create: false\n      mgmt:\n        replicate:\n          namespace: [\"qwe\"]\n    asserts:\n      - hasDocuments:\n          count: 0\n  - it: should not create cluster role if mgmt disabled\n    set:\n      mgmt:\n        enabled: false\n        replicate:\n          namespace: [\"qwe\"]\n    asserts:\n      - hasDocuments:\n          count: 0\n"
  },
  {
    "path": "test/unit/sa.yaml",
    "content": "suite: test serviceaccount annotations\ntemplates:\n  - serviceaccount.yaml\ntests:\n  - it: should omit serviceaccount annotations by default\n    asserts:\n      - notExists:\n          path: metadata.annotations\n  - it: should render serviceaccount annotations when provided\n    set:\n      serviceAccount:\n        annotations:\n          foo: bar\n    asserts:\n      - exists:\n          path: metadata.annotations\n      - equal:\n          path: metadata.annotations.foo\n          value: bar\n"
  },
  {
    "path": "test/unit/service.yaml",
    "content": "suite: test service definition\ntemplates:\n  - service.yaml\ntests:\n  - it: should omit service annotations when null\n    set:\n      service.annotations: null\n    asserts:\n      - notExists:\n          path: metadata.annotations.foo\n  - it: should set service annotations\n    set:\n      service.annotations:\n        foo: bar\n    asserts:\n      - exists:\n          path: metadata.annotations.foo\n      - equal:\n          path: metadata.annotations.foo\n          value: bar\n  - it: should omit trafficDistribution by default\n    asserts:\n      - notExists:\n          path: spec.trafficDistribution\n  - it: should omit trafficDistribution when null\n    set:\n      service.trafficDistribution: null\n    asserts:\n      - notExists:\n          path: spec.trafficDistribution\n"
  },
  {
    "path": "test/unit/tsc.yaml",
    "content": "suite: test topologySpreadConstraints\ntemplates:\n  - deployment.yaml\ntests:\n  - it: renders with DoNotSchedule policy\n    set:\n      topologySpreadConstraints:\n        - maxSkew: 1\n          topologyKey: \"kubernetes.io/hostname\"\n          whenUnsatisfiable: \"DoNotSchedule\"\n    asserts:\n      - isKind:\n          of: Deployment\n      - contains:\n          path: spec.template.spec.topologySpreadConstraints\n          content:\n            maxSkew: 1\n            topologyKey: \"kubernetes.io/hostname\"\n            whenUnsatisfiable: \"DoNotSchedule\"\n\n  - it: renders multiple topologySpreadConstraints\n    set:\n      topologySpreadConstraints:\n        - maxSkew: 1\n          topologyKey: \"traffic.kubernetes.io/region\"\n          whenUnsatisfiable: \"ScheduleAnyway\"\n        - maxSkew: 2\n          topologyKey: \"topology.kubernetes.io/zone\"\n          whenUnsatisfiable: \"DoNotSchedule\"\n    asserts:\n      - lengthEqual:\n          path: spec.template.spec.topologySpreadConstraints\n          count: 2\n      - contains:\n          path: spec.template.spec.topologySpreadConstraints\n          content:\n            topologyKey: \"traffic.kubernetes.io/region\"\n          any: true\n      - contains:\n          path: spec.template.spec.topologySpreadConstraints\n          content:\n            topologyKey: \"topology.kubernetes.io/zone\"\n          any: true\n\n  - it: renders with labelSelector\n    set:\n      topologySpreadConstraints:\n        - maxSkew: 1\n          topologyKey: \"kubernetes.io/hostname\"\n          whenUnsatisfiable: \"DoNotSchedule\"\n          labelSelector:\n            matchLabels:\n              app: opa-kube-mgmt\n    asserts:\n      - contains:\n          path: spec.template.spec.topologySpreadConstraints\n          content:\n            maxSkew: 1\n            topologyKey: \"kubernetes.io/hostname\"\n            whenUnsatisfiable: \"DoNotSchedule\"\n            labelSelector:\n              matchLabels:\n                app: opa-kube-mgmt\n\n  - it: renders with minDomains\n    set:\n      topologySpreadConstraints:\n        - maxSkew: 1\n          topologyKey: \"kubernetes.io/hostname\"\n          whenUnsatisfiable: \"DoNotSchedule\"\n          minDomains: 3\n    asserts:\n      - contains:\n          path: spec.template.spec.topologySpreadConstraints\n          content:\n            minDomains: 3\n          any: true\n\n  - it: passes through constraint with labelSelector\n    set:\n      topologySpreadConstraints:\n        - maxSkew: 1\n          topologyKey: \"kubernetes.io/hostname\"\n          whenUnsatisfiable: \"DoNotSchedule\"\n          labelSelector:\n            matchLabels:\n              app: opa-kube-mgmt\n            matchExpressions:\n              - key: environment\n                operator: In\n                values:\n                  - production\n                  - staging\n    asserts:\n      - equal:\n          path: spec.template.spec.topologySpreadConstraints[0].labelSelector\n          value:\n            matchLabels:\n              app: opa-kube-mgmt\n            matchExpressions:\n              - key: environment\n                operator: In\n                values:\n                  - production\n                  - staging\n\n  - it: passes through constraint with nodeAffinityPolicy\n    set:\n      topologySpreadConstraints:\n        - maxSkew: 1\n          topologyKey: \"kubernetes.io/hostname\"\n          whenUnsatisfiable: \"DoNotSchedule\"\n          nodeAffinityPolicy: \"Honor\"\n    asserts:\n      - equal:\n          path: spec.template.spec.topologySpreadConstraints[0].nodeAffinityPolicy\n          value: \"Honor\"\n\n  - it: passes through constraint with nodeTaintsPolicy\n    set:\n      topologySpreadConstraints:\n        - maxSkew: 1\n          topologyKey: \"kubernetes.io/hostname\"\n          whenUnsatisfiable: \"DoNotSchedule\"\n          nodeTaintsPolicy: \"Ignore\"\n    asserts:\n      - equal:\n          path: spec.template.spec.topologySpreadConstraints[0].nodeTaintsPolicy\n          value: \"Ignore\"\n\n  - it: passes through constraint with matchLabelKeys\n    set:\n      topologySpreadConstraints:\n        - maxSkew: 1\n          topologyKey: \"kubernetes.io/hostname\"\n          whenUnsatisfiable: \"DoNotSchedule\"\n          matchLabelKeys:\n            - config\n    asserts:\n      - equal:\n          path: spec.template.spec.topologySpreadConstraints[0].matchLabelKeys\n          value:\n            - config\n\n  - it: passes through fully configured constraint\n    set:\n      topologySpreadConstraints:\n        - maxSkew: 2\n          topologyKey: \"topology.kubernetes.io/zone\"\n          whenUnsatisfiable: \"ScheduleAnyway\"\n          minDomains: 3\n          nodeAffinityPolicy: \"Honor\"\n          nodeTaintsPolicy: \"Honor\"\n          matchLabelKeys:\n            - pod-template-hash\n          labelSelector:\n            matchLabels:\n              app: opa-kube-mgmt\n    asserts:\n      - equal:\n          path: spec.template.spec.topologySpreadConstraints[0]\n          value:\n            maxSkew: 2\n            topologyKey: \"topology.kubernetes.io/zone\"\n            whenUnsatisfiable: \"ScheduleAnyway\"\n            minDomains: 3\n            nodeAffinityPolicy: \"Honor\"\n            nodeTaintsPolicy: \"Honor\"\n            matchLabelKeys:\n              - pod-template-hash\n            labelSelector:\n              matchLabels:\n                app: opa-kube-mgmt\n"
  }
]