[
  {
    "path": ".github/workflows/pr.yaml",
    "content": "name: pull request\non:\n  pull_request:\n    branches:\n      - master\n      - main\n\njobs:\n  setup:\n    runs-on: ubuntu-latest\n    name: setup\n    env:\n      BUILD_PLATFORMS: \"linux/amd64,linux/arm64,linux/ppc64le,linux/s390x\"\n      GO_VERSION: \"~1.21\"\n    steps:\n      - name: Setting Workflow Variables\n        id: set-variables\n        run: |\n          echo \"::set-output name=repository_name::$(basename $GITHUB_REPOSITORY)\"\n          echo \"::set-output name=bin_dir::$(pwd)/bin\"\n          \n          # Create Distribution Matrix\n          echo \"::set-output name=dist_matrix::$(echo -n \"${{ env.BUILD_PLATFORMS }}\" | jq -csR '. | split(\",\")')\"\n\n          # Set versions based on presence of tag\n          if [[ \"${{ github.ref }}\" =~ ^refs/tags/ ]]; then\n            TAG=\"${GITHUB_REF/refs\\/tags\\//}\"\n            echo \"::set-output name=tag_event::true\"\n            echo \"::set-output name=operator_version::$TAG\"\n          else\n            echo \"::set-output name=tag_event::false\"\n            echo \"::set-output name=operator_version::$DEFAULT_OPERATOR_VERSION\"\n          fi\n\n      - name: Build Go Cache Paths\n        id: go-cache-paths\n        run: |\n          echo \"::set-output name=go-build::$(go env GOCACHE)\"\n          echo \"::set-output name=go-mod::$(go env GOMODCACHE)\"\n\n      - name: Set up Go 1.x\n        uses: actions/setup-go@v1\n        with:\n          go-version: ${{ inputs.GO_VERSION }}\n\n      - name: Check out code\n        uses: actions/checkout@v2\n\n      - name: Go Build Cache\n        uses: actions/cache@v2\n        with:\n          path: ${{ steps.go-cache-paths.outputs.go-build }}\n          key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }}\n\n      - name: Go Mod Cache\n        uses: actions/cache@v2\n        with:\n          path: ${{ steps.go-cache-paths.outputs.go-mod }}\n          key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}\n\n      - name: Go Dependencies\n        run: go mod download\n\n      - name: Download Binaries\n        env:\n          OPERATOR_SDK_VERSION: ${{ inputs.OPERATOR_SDK_VERSION }}\n        run: |\n          # Create Binary Directory\n          mkdir -p ${{ steps.set-variables.outputs.bin_dir }}\n          # Operator SDK\n          curl -L -o ${{ steps.set-variables.outputs.bin_dir }}/operator-sdk https://github.com/operator-framework/operator-sdk/releases/download/${{ env.OPERATOR_SDK_VERSION }}/operator-sdk_linux_amd64\n          # Controller-gen\n          make controller-gen\n          # Kustomize\n          make kustomize\n\n      - name: Upload Support Binaries\n        uses: actions/upload-artifact@v2\n        with:\n          name: support-binaries\n          path: ${{ steps.set-variables.outputs.bin_dir }}\n\n    outputs:\n      repository_name: ${{ steps.set-variables.outputs.repository_name }}\n      bin_dir: ${{ steps.set-variables.outputs.bin_dir }}\n      go_build: ${{ steps.go-cache-paths.outputs.go-build }}\n      go_mod: ${{ steps.go-cache-paths.outputs.go-mod }}\n      tag_event: ${{ steps.set-variables.outputs.tag_event }}\n      dist_matrix: ${{ steps.set-variables.outputs.dist_matrix }}\n\n  build-operator:\n    runs-on: ubuntu-latest\n    name: build-operator\n    needs: [\"setup\"]\n    strategy:\n      matrix:\n        platform: ${{ fromJson(needs.setup.outputs.dist_matrix) }}\n    env:\n      REPOSITORY_NAME: ${{ needs.setup.outputs.repository_name }}\n    steps:\n      - name: Set up Go 1.x\n        uses: actions/setup-go@v1\n        with:\n          go-version: ${{ inputs.GO_VERSION }}\n\n      - name: Check out code\n        uses: actions/checkout@v2\n\n      - name: Go Build Cache\n        uses: actions/cache@v2\n        with:\n          path: ${{ needs.setup.outputs.go_build }}\n          key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }}\n\n      - name: Go Mod Cache\n        uses: actions/cache@v2\n        with:\n          path: ${{ needs.setup.outputs.go_mod }}\n          key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}\n\n      - name: Download Support Binaries\n        uses: actions/download-artifact@v2\n        with:\n          name: support-binaries\n          path: ${{ needs.setup.outputs.bin_dir }}\n\n      - name: Prepare Build Step\n        id: setup-build-step\n        run: |\n          # Setup Path\n          echo \"${{ needs.setup.outputs.bin_dir }}\" >> $GITHUB_PATH\n          # Make Binaries Executable\n          chmod +x ${{ needs.setup.outputs.bin_dir }}/*\n          # Configure Platform Variables\n          echo \"::set-output name=platform_os::$(echo ${{ matrix.platform }} |  cut -d/ -f1)\"\n          echo \"::set-output name=platform_arch::$(echo ${{ matrix.platform }} |  cut -d/ -f2)\"\n\n      - name: Download Dependencies\n        shell: bash\n        run: |\n          make generate\n          make fmt\n          make vet\n\n      - name: build code\n        shell: bash\n        env:\n          VERSION: latest\n          GOOS: ${{ steps.setup-build-step.outputs.platform_os }}\n          GOARCH: ${{ steps.setup-build-step.outputs.platform_arch }}\n        run: make\n\n  test-operator:\n    runs-on: ubuntu-latest\n    name: test-operator\n    needs: [\"setup\"]\n    env:\n      REPOSITORY_NAME: ${{ needs.setup.outputs.repository_name }}\n    steps:\n      - name: Set up Go 1.x\n        uses: actions/setup-go@v1\n        with:\n          go-version: ${{ inputs.GO_VERSION }}\n\n      - name: Check out code\n        uses: actions/checkout@v2\n\n      - name: Go Build Cache\n        uses: actions/cache@v2\n        with:\n          path: ${{ needs.setup.outputs.go_build }}\n          key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }}\n\n      - name: Go Mod Cache\n        uses: actions/cache@v2\n        with:\n          path: ${{ needs.setup.outputs.go_mod }}\n          key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}\n\n      - name: Download Binaries\n        uses: actions/download-artifact@v2\n        with:\n          name: support-binaries\n          path: ${{ needs.setup.outputs.bin_dir }}\n\n      - name: Prepare Build Step\n        id: setup-build-step\n        run: |\n          # Setup Path\n          echo \"${{ needs.setup.outputs.bin_dir }}\" >> $GITHUB_PATH\n          # Make Binaries Executable\n          chmod +x ${{ needs.setup.outputs.bin_dir }}/*\n\n      - name: Run unit tests\n        shell: bash\n        run: make test\n"
  },
  {
    "path": ".github/workflows/push.yaml",
    "content": "name: push\non:\n  push:\n    branches:\n      - main\n      - master\n    tags:\n      - v*\n\njobs:\n  setup:\n    runs-on: ubuntu-latest\n    name: setup\n    env:\n      BUILD_PLATFORMS: \"linux/amd64,linux/arm64,linux/ppc64le,linux/s390x\"\n      GO_VERSION: \"~1.21\"\n    steps:\n      - name: Setting Workflow Variables\n        id: set-variables\n        run: |\n          echo \"::set-output name=repository_name::$(basename $GITHUB_REPOSITORY)\"\n          echo \"::set-output name=bin_dir::$(pwd)/bin\"\n          \n          # Create Distribution Matrix\n          echo \"::set-output name=dist_matrix::$(echo -n \"${{ env.BUILD_PLATFORMS }}\" | jq -csR '. | split(\",\")')\"\n\n          # Set versions based on presence of tag\n          if [[ \"${{ github.ref }}\" =~ ^refs/tags/ ]]; then\n            TAG=\"${GITHUB_REF/refs\\/tags\\//}\"\n            echo \"::set-output name=tag_event::true\"\n            echo \"::set-output name=operator_version::$TAG\"\n          else\n            echo \"::set-output name=tag_event::false\"\n            echo \"::set-output name=operator_version::$DEFAULT_OPERATOR_VERSION\"\n          fi\n\n      - name: Build Go Cache Paths\n        id: go-cache-paths\n        run: |\n          echo \"::set-output name=go-build::$(go env GOCACHE)\"\n          echo \"::set-output name=go-mod::$(go env GOMODCACHE)\"\n\n      - name: Set up Go 1.x\n        uses: actions/setup-go@v1\n        with:\n          go-version: ${{ inputs.GO_VERSION }}\n\n      - name: Check out code\n        uses: actions/checkout@v2\n\n      - name: Go Build Cache\n        uses: actions/cache@v2\n        with:\n          path: ${{ steps.go-cache-paths.outputs.go-build }}\n          key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }}\n\n      - name: Go Mod Cache\n        uses: actions/cache@v2\n        with:\n          path: ${{ steps.go-cache-paths.outputs.go-mod }}\n          key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}\n\n      - name: Go Dependencies\n        run: go mod download\n\n      - name: Download Binaries\n        env:\n          OPERATOR_SDK_VERSION: ${{ inputs.OPERATOR_SDK_VERSION }}\n        run: |\n          # Create Binary Directory\n          mkdir -p ${{ steps.set-variables.outputs.bin_dir }}\n          # Operator SDK\n          curl -L -o ${{ steps.set-variables.outputs.bin_dir }}/operator-sdk https://github.com/operator-framework/operator-sdk/releases/download/${{ env.OPERATOR_SDK_VERSION }}/operator-sdk_linux_amd64\n          # Controller-gen\n          make controller-gen\n          # Kustomize\n          make kustomize\n\n      - name: Upload Support Binaries\n        uses: actions/upload-artifact@v2\n        with:\n          name: support-binaries\n          path: ${{ steps.set-variables.outputs.bin_dir }}\n\n    outputs:\n      repository_name: ${{ steps.set-variables.outputs.repository_name }}\n      bin_dir: ${{ steps.set-variables.outputs.bin_dir }}\n      go_build: ${{ steps.go-cache-paths.outputs.go-build }}\n      go_mod: ${{ steps.go-cache-paths.outputs.go-mod }}\n      tag_event: ${{ steps.set-variables.outputs.tag_event }}\n      dist_matrix: ${{ steps.set-variables.outputs.dist_matrix }}\n\n  build-operator:\n    runs-on: ubuntu-latest\n    name: build-operator\n    needs: [\"setup\"]\n    strategy:\n      matrix:\n        platform: ${{ fromJson(needs.setup.outputs.dist_matrix) }}\n    env:\n      REPOSITORY_NAME: ${{ needs.setup.outputs.repository_name }}\n    steps:\n      - name: Set up Go 1.x\n        uses: actions/setup-go@v1\n        with:\n          go-version: ${{ inputs.GO_VERSION }}\n\n      - name: Check out code\n        uses: actions/checkout@v2\n\n      - name: Go Build Cache\n        uses: actions/cache@v2\n        with:\n          path: ${{ needs.setup.outputs.go_build }}\n          key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }}\n\n      - name: Go Mod Cache\n        uses: actions/cache@v2\n        with:\n          path: ${{ needs.setup.outputs.go_mod }}\n          key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}\n\n      - name: Download Support Binaries\n        uses: actions/download-artifact@v2\n        with:\n          name: support-binaries\n          path: ${{ needs.setup.outputs.bin_dir }}\n\n      - name: Prepare Build Step\n        id: setup-build-step\n        run: |\n          # Setup Path\n          echo \"${{ needs.setup.outputs.bin_dir }}\" >> $GITHUB_PATH\n          # Make Binaries Executable\n          chmod +x ${{ needs.setup.outputs.bin_dir }}/*\n          # Configure Platform Variables\n          echo \"::set-output name=platform_os::$(echo ${{ matrix.platform }} |  cut -d/ -f1)\"\n          echo \"::set-output name=platform_arch::$(echo ${{ matrix.platform }} |  cut -d/ -f2)\"\n\n      - name: Download Dependencies\n        shell: bash\n        run: |\n          make generate\n          make fmt\n          make vet\n\n      - name: build code\n        shell: bash\n        env:\n          VERSION: latest\n          GOOS: ${{ steps.setup-build-step.outputs.platform_os }}\n          GOARCH: ${{ steps.setup-build-step.outputs.platform_arch }}\n        run: make\n\n  test-operator:\n    runs-on: ubuntu-latest\n    name: test-operator\n    needs: [\"setup\"]\n    env:\n      REPOSITORY_NAME: ${{ needs.setup.outputs.repository_name }}\n    steps:\n      - name: Set up Go 1.x\n        uses: actions/setup-go@v1\n        with:\n          go-version: ${{ inputs.GO_VERSION }}\n\n      - name: Check out code\n        uses: actions/checkout@v2\n\n      - name: Go Build Cache\n        uses: actions/cache@v2\n        with:\n          path: ${{ needs.setup.outputs.go_build }}\n          key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }}\n\n      - name: Go Mod Cache\n        uses: actions/cache@v2\n        with:\n          path: ${{ needs.setup.outputs.go_mod }}\n          key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}\n\n      - name: Download Binaries\n        uses: actions/download-artifact@v2\n        with:\n          name: support-binaries\n          path: ${{ needs.setup.outputs.bin_dir }}\n\n      - name: Prepare Build Step\n        id: setup-build-step\n        run: |\n          # Setup Path\n          echo \"${{ needs.setup.outputs.bin_dir }}\" >> $GITHUB_PATH\n          # Make Binaries Executable\n          chmod +x ${{ needs.setup.outputs.bin_dir }}/*\n\n      - name: Run unit tests\n        shell: bash\n        run: make test\n\n  github-release:\n    runs-on: ubuntu-latest\n    name: github-release\n    if: ${{ needs.setup.outputs.tag_event == 'true' }}\n    needs:\n      [\n        \"setup\",\n        \"test-operator\",\n        \"build-operator\",\n      ]\n    steps:\n      - name: Check out code\n        uses: actions/checkout@v2\n\n      - name: Create Release\n        uses: softprops/action-gh-release@v1\n        with:\n          generate_release_notes: true\n          draft: false\n          prerelease: false\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "\n# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\nbin\n\n# Test binary, build with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Kubernetes Generated files - skip generated files, except for vendored files\n\n!vendor/**/zz_generated.*\n\n# editor and IDE paraphernalia\n.idea\n*.swp\n*.swo\n*~\n\nbundle/\nbundle.Dockerfile"
  },
  {
    "path": "Dockerfile",
    "content": "# Build the manager binary\nFROM golang:1.18 as builder\n\nWORKDIR /workspace\n# Copy the Go Modules manifests\nCOPY go.mod go.mod\nCOPY go.sum go.sum\n# cache deps before building and copying source so that we don't need to re-download as much\n# and so that source changes don't invalidate our downloaded layer\nRUN go mod download\n\n# Copy the go source\nCOPY main.go main.go\nCOPY api/ api/\nCOPY controllers/ controllers/\nCOPY pkg/ pkg/\n\n# Build\nRUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o manager main.go\n\n# Use distroless as minimal base image to package the manager binary\n# Refer to https://github.com/GoogleContainerTools/distroless for more details\nFROM registry.access.redhat.com/ubi8/ubi-minimal\nWORKDIR /\nCOPY --from=builder /workspace/manager .\nUSER 65532:65532\n\nENTRYPOINT [\"/manager\"]\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": "Makefile",
    "content": "# VERSION defines the project version for the bundle.\n# Update this value when you upgrade the version of your project.\n# To re-generate a bundle for another specific version without changing the standard setup, you can:\n# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2)\n# - use environment variables to overwrite this value (e.g export VERSION=0.0.2)\nVERSION ?= 0.0.1\n\n# CHANNELS define the bundle channels used in the bundle.\n# Add a new line here if you would like to change its default config. (E.g CHANNELS = \"candidate,fast,stable\")\n# To re-generate a bundle for other specific channels without changing the standard setup, you can:\n# - use the CHANNELS as arg of the bundle target (e.g make bundle CHANNELS=candidate,fast,stable)\n# - use environment variables to overwrite this value (e.g export CHANNELS=\"candidate,fast,stable\")\nifneq ($(origin CHANNELS), undefined)\nBUNDLE_CHANNELS := --channels=$(CHANNELS)\nendif\n\n# DEFAULT_CHANNEL defines the default channel used in the bundle.\n# Add a new line here if you would like to change its default config. (E.g DEFAULT_CHANNEL = \"stable\")\n# To re-generate a bundle for any other default channel without changing the default setup, you can:\n# - use the DEFAULT_CHANNEL as arg of the bundle target (e.g make bundle DEFAULT_CHANNEL=stable)\n# - use environment variables to overwrite this value (e.g export DEFAULT_CHANNEL=\"stable\")\nifneq ($(origin DEFAULT_CHANNEL), undefined)\nBUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL)\nendif\nBUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL)\n\n# IMAGE_TAG_BASE defines the docker.io namespace and part of the image name for remote images.\n# This variable is used to construct full image tags for bundle and catalog images.\n#\n# For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both\n# example.com/memcached-operator-bundle:$VERSION and example.com/memcached-operator-catalog:$VERSION.\nIMAGE_TAG_BASE ?= example.com/memcached-operator\n\n# BUNDLE_IMG defines the image:tag used for the bundle.\n# You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=<some-registry>/<project-name-bundle>:<tag>)\nBUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION)\n\n# BUNDLE_GEN_FLAGS are the flags passed to the operator-sdk generate bundle command\nBUNDLE_GEN_FLAGS ?= -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS)\n\n# USE_IMAGE_DIGESTS defines if images are resolved via tags or digests\n# You can enable this value if you would like to use SHA Based Digests\n# To enable set flag to true\nUSE_IMAGE_DIGESTS ?= false\nifeq ($(USE_IMAGE_DIGESTS), true)\n\tBUNDLE_GEN_FLAGS += --use-image-digests\nendif\n\n# Image URL to use all building/pushing image targets\nIMG ?= controller:latest\n# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.\nENVTEST_K8S_VERSION = 1.24.1\n\n# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)\nifeq (,$(shell go env GOBIN))\nGOBIN=$(shell go env GOPATH)/bin\nelse\nGOBIN=$(shell go env GOBIN)\nendif\n\n# Setting SHELL to bash allows bash commands to be executed by recipes.\n# This is a requirement for 'setup-envtest.sh' in the test target.\n# Options are set to exit when a recipe line exits non-zero or a piped command fails.\nSHELL = /usr/bin/env bash -o pipefail\n.SHELLFLAGS = -ec\n\nCHART_REPO_URL ?= http://example.com\nHELM_REPO_DEST ?= /tmp/gh-pages\n\n.PHONY: all\nall: build\n\n##@ General\n\n# The help target prints out all targets with their descriptions organized\n# beneath their categories. The categories are represented by '##@' and the\n# target descriptions by '##'. The awk commands is responsible for reading the\n# entire set of makefiles included in this invocation, looking for lines of the\n# file as xyz: ## something, and then pretty-format the target and help. Then,\n# if there's a line with ##@ something, that gets pretty-printed as a category.\n# More info on the usage of ANSI control characters for terminal formatting:\n# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters\n# More info on the awk command:\n# http://linuxcommand.org/lc3_adv_awk.php\n\n.PHONY: help\nhelp: ## Display this help.\n\t@awk 'BEGIN {FS = \":.*##\"; printf \"\\nUsage:\\n  make \\033[36m<target>\\033[0m\\n\"} /^[a-zA-Z_0-9-]+:.*?##/ { printf \"  \\033[36m%-15s\\033[0m %s\\n\", $$1, $$2 } /^##@/ { printf \"\\n\\033[1m%s\\033[0m\\n\", substr($$0, 5) } ' $(MAKEFILE_LIST)\n\n##@ Development\n\n.PHONY: manifests\nmanifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.\n\t$(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths=\"./...\" output:crd:artifacts:config=config/crd/bases\n\n.PHONY: generate\ngenerate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.\n\t$(CONTROLLER_GEN) object:headerFile=\"hack/boilerplate.go.txt\" paths=\"./...\"\n\n.PHONY: fmt\nfmt: ## Run go fmt against code.\n\tgo fmt ./...\n\n.PHONY: vet\nvet: ## Run go vet against code.\n\tgo vet ./...\n\n.PHONY: test\ntest: manifests generate fmt vet envtest ## Run tests.\n\tKUBEBUILDER_ASSETS=\"$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)\" go test ./... -coverprofile cover.out\n\n##@ Build\n\n.PHONY: build\nbuild: generate fmt vet ## Build manager binary.\n\tgo build -o bin/manager main.go\n\n.PHONY: run\nrun: manifests generate fmt vet ## Run a controller from your host.\n\tgo run ./main.go\n\n.PHONY: docker-build\ndocker-build: test ## Build docker image with the manager.\n\tdocker build -t ${IMG} .\n\n.PHONY: docker-push\ndocker-push: ## Push docker image with the manager.\n\tdocker push ${IMG}\n\n##@ Deployment\n\nifndef ignore-not-found\n  ignore-not-found = false\nendif\n\n.PHONY: install\ninstall: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.\n\t$(KUSTOMIZE) build config/crd | kubectl apply -f -\n\n.PHONY: uninstall\nuninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.\n\t$(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f -\n\n.PHONY: deploy\ndeploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.\n\tcd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}\n\t$(KUSTOMIZE) build config/default | kubectl apply -f -\n\n.PHONY: undeploy\nundeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.\n\t$(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f -\n\n##@ Build Dependencies\n\n## Location to install dependencies to\nLOCALBIN ?= $(shell pwd)/bin\n$(LOCALBIN):\n\tmkdir -p $(LOCALBIN)\n\n## Tool Binaries\nKUSTOMIZE ?= $(LOCALBIN)/kustomize\nCONTROLLER_GEN ?= $(LOCALBIN)/controller-gen\nENVTEST ?= $(LOCALBIN)/setup-envtest\n\n## Tool Versions\nKUSTOMIZE_VERSION ?= v3.8.7\nCONTROLLER_TOOLS_VERSION ?= v0.9.0\n\nKUSTOMIZE_INSTALL_SCRIPT ?= \"https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh\"\n.PHONY: kustomize\nkustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.\n$(KUSTOMIZE): $(LOCALBIN)\n\tcurl -s $(KUSTOMIZE_INSTALL_SCRIPT) | bash -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(LOCALBIN)\n\n.PHONY: controller-gen\ncontroller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.\n$(CONTROLLER_GEN): $(LOCALBIN)\n\tGOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION)\n\n.PHONY: envtest\nenvtest: $(ENVTEST) ## Download envtest-setup locally if necessary.\n$(ENVTEST): $(LOCALBIN)\n\tGOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest\n\n.PHONY: bundle\nbundle: manifests kustomize ## Generate bundle manifests and metadata, then validate generated files.\n\toperator-sdk generate kustomize manifests --interactive=false -q\n\tcd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG)\n\t$(KUSTOMIZE) build config/manifests | operator-sdk generate bundle $(BUNDLE_GEN_FLAGS)\n\toperator-sdk bundle validate ./bundle\n\n.PHONY: bundle-build\nbundle-build: ## Build the bundle image.\n\tdocker build -f bundle.Dockerfile -t $(BUNDLE_IMG) .\n\n.PHONY: bundle-push\nbundle-push: ## Push the bundle image.\n\t$(MAKE) docker-push IMG=$(BUNDLE_IMG)\n\n.PHONY: opm\nOPM = ./bin/opm\nopm: ## Download opm locally if necessary.\nifeq (,$(wildcard $(OPM)))\nifeq (,$(shell which opm 2>/dev/null))\n\t@{ \\\n\tset -e ;\\\n\tmkdir -p $(dir $(OPM)) ;\\\n\tOS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \\\n\tcurl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.23.0/$${OS}-$${ARCH}-opm ;\\\n\tchmod +x $(OPM) ;\\\n\t}\nelse\nOPM = $(shell which opm)\nendif\nendif\n\n# A comma-separated list of bundle images (e.g. make catalog-build BUNDLE_IMGS=example.com/operator-bundle:v0.1.0,example.com/operator-bundle:v0.2.0).\n# These images MUST exist in a registry and be pull-able.\nBUNDLE_IMGS ?= $(BUNDLE_IMG)\n\n# The image tag given to the resulting catalog image (e.g. make catalog-build CATALOG_IMG=example.com/operator-catalog:v0.2.0).\nCATALOG_IMG ?= $(IMAGE_TAG_BASE)-catalog:v$(VERSION)\n\n# Set CATALOG_BASE_IMG to an existing catalog image tag to add $BUNDLE_IMGS to that image.\nifneq ($(origin CATALOG_BASE_IMG), undefined)\nFROM_INDEX_OPT := --from-index $(CATALOG_BASE_IMG)\nendif\n\n# Build a catalog image by adding bundle images to an empty catalog using the operator package manager tool, 'opm'.\n# This recipe invokes 'opm' in 'semver' bundle add mode. For more information on add modes, see:\n# https://github.com/operator-framework/community-operators/blob/7f1438c/docs/packaging-operator.md#updating-your-existing-operator\n.PHONY: catalog-build\ncatalog-build: opm ## Build a catalog image.\n\t$(OPM) index add --container-tool docker --mode semver --tag $(CATALOG_IMG) --bundles $(BUNDLE_IMGS) $(FROM_INDEX_OPT)\n\n# Push the catalog image.\n.PHONY: catalog-push\ncatalog-push: ## Push a catalog image.\n\t$(MAKE) docker-push IMG=$(CATALOG_IMG)\n\n# Generate helm chart\n.PHONY: kustomize\nhelmchart: kustomize\n\tmkdir -p ./charts/${OPERATOR_NAME}/templates\n\tcp ./config/helmchart/templates/* ./charts/${OPERATOR_NAME}/templates\n\t$(KUSTOMIZE) build ./config/helmchart | sed 's/release-namespace/{{.Release.Namespace}}/' > ./charts/${OPERATOR_NAME}/templates/rbac.yaml\n\tversion=${VERSION} envsubst < ./config/helmchart/Chart.yaml.tpl  > ./charts/${OPERATOR_NAME}/Chart.yaml\n\tversion=${VERSION} image_repo=$${IMG%:*} envsubst < ./config/helmchart/values.yaml.tpl  > ./charts/${OPERATOR_NAME}/values.yaml\n\thelm lint ./charts/${OPERATOR_NAME}\t\n\n.PHONY: helmchart\nhelmchart-repo: helmchart\n\tmkdir -p ${HELM_REPO_DEST}/${OPERATOR_NAME}\n\thelm package -d ${HELM_REPO_DEST}/${OPERATOR_NAME} ./charts/${OPERATOR_NAME}\n\thelm repo index --url ${CHART_REPO_URL} ${HELM_REPO_DEST}\n\n.PHONY: helmchart-repo\nhelmchart-repo-push: helmchart-repo\t\n\tgit -C ${HELM_REPO_DEST} add .\n\tgit -C ${HELM_REPO_DEST} status\n\tgit -C ${HELM_REPO_DEST} commit -m \"Release ${VERSION}\"\n\tgit -C ${HELM_REPO_DEST} push origin \"gh-pages\"\t\n"
  },
  {
    "path": "PROJECT",
    "content": "domain: example.io\nlayout:\n- go.kubebuilder.io/v3\nprojectName: operator-utils\nrepo: github.com/redhat-cop/operator-utils\nresources:\n- domain: example.io\n  group: operator-utils\n  kind: MyCRD\n  path: github.com/redhat-cop/operator-utils/api/v1alpha1\n  version: v1alpha1\n- domain: example.io\n  group: operator-utils\n  kind: EnforcingCRD\n  path: github.com/redhat-cop/operator-utils/api/v1alpha1\n  version: v1alpha1\n- domain: example.io\n  group: operator-utils\n  kind: EnforcingPatch\n  path: github.com/redhat-cop/operator-utils/api/v1alpha1\n  version: v1alpha1\n- domain: example.io\n  group: operator-utils\n  kind: EnforcingPatch\n  path: github.com/redhat-cop/operator-utils/api/v1alpha1\n  version: v1alpha1       \nversion: \"3\"\nplugins:\n  manifests.sdk.operatorframework.io/v2: {}\n  scorecard.sdk.operatorframework.io/v2: {}\n"
  },
  {
    "path": "README.md",
    "content": "# Operator Utility Library\n\n![build status](https://github.com/redhat-cop/operator-utils/workflows/push/badge.svg)\n[![GoDoc reference](https://img.shields.io/badge/godoc-reference-blue.svg)](https://pkg.go.dev/github.com/redhat-cop/operator-utils)\n[![Go Report Card](https://goreportcard.com/badge/github.com/redhat-cop/operator-utils)](https://goreportcard.com/report/github.com/redhat-cop/operator-utils)\n![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/redhat-cop/operator-utils)\n\nThis library layers on top of the Operator SDK and with the objective of helping writing better and more consistent operators.\n\n*NOTICE* versions of this library up to `v0.3.7` are compatible with [operator-sdk](https://github.com/operator-framework/operator-sdk) `0.x`, starting from version v0.4.0 this library will be compatible only with [operator-sdk](https://github.com/operator-framework/operator-sdk) 1.x.\n\n## Scope of this library\n\nThis library covers three main areas:\n\n1. [Utility Methods](#Utility-Methods) Utility methods that are callable by any operator.\n2. [Idempotent methods](#Idempotent-Methods-to-Manipulate-Resources) to manipulate resources and arrays of resources\n3. [Basic operator lifecycle](#Basic-Operator-Lifecycle-Management) needs (validation, initialization, status and error management, finalization)\n4. [Enforcing resources operator support](#Enforcing-Resource-Operator-Support). For those operators which calculate a set of resources that need to exist and then enforce them, generalized support for the enforcing phase is provided.\n\n## Utility Methods\n\nPrior to version v1.3.x the general philosophy of this library was that new operator would inherit from `ReconcilerBase` and in doing so they would have access to a bunch of utility methods.\nWith release v1.3.0 a new approach is available. Utility methods are callable by any operator without having to inherit. This makes it easier to use this library and does not conflict with autogenerate code from `kube-builder` and `operator-sdk`.\nMost of the Utility methods receive a context.Context parameter. Normally this context must be initialized with a `logr.Logger` and a `rest.Config`. Some utility methods may require more, see each individual documentation.\n\nUtility methods are currently organized in the following folders:\n\n1. crud: idempotent create/update/delete functions.\n2. discoveryclient: methods related to the discovery client, typically used to load `apiResource` objects.\n3. dynamicclient: methods related to building client based on object whose type is not known at compile time.\n4. templates: utility methods for dealing with templates whose output is an object or a list of objects.\n\n## Idempotent Methods to Manipulate Resources\n\nThe following idempotent methods are provided (and their corresponding array version):\n\n1. createIfNotExists\n2. createOrUpdate\n3. deleteIfExists\n\nAlso there are utility methods to manage finalizers, test ownership and process templates of resources.\n\n## Basic Operator Lifecycle Management\n\n---\n\nNote\n\nThis part of the library is largely deprecated. For initialization and defaulting a MutatingWebHook should be used. For validation a Validating WebHook should be used.\nThe part regarding the finalization is still relevant.\n\n---\n\nTo get started with this library do the following:\n\nChange your reconciler initialization as exemplified below to add a set of utility methods to it\n\n```go\nimport \"github.com/redhat-cop/operator-utils/pkg/util\"\n\n...\ntype MyReconciler struct {\n  util.ReconcilerBase\n  Log logr.Logger\n  ... other optional fields ...\n}\n```\n\nin main.go change like this\n\n```go\n  if err = (&controllers.MyReconciler{\n    ReconcilerBase: util.NewReconcilerBase(mgr.GetClient(), mgr.GetScheme(), mgr.GetConfig(), mgr.GetEventRecorderFor(\"My_controller\"), mgr.GetAPIReader()),\n    Log:            ctrl.Log.WithName(\"controllers\").WithName(\"My\"),\n  }).SetupWithManager(mgr); err != nil {\n    setupLog.Error(err, \"unable to create controller\", \"controller\", \"My\")\n    os.Exit(1)\n  }\n```\n\nAlso make sure to create the manager with `configmap` as the lease option for leader election:\n\n```go\n  mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{\n    Scheme:                     scheme,\n    MetricsBindAddress:         metricsAddr,\n    Port:                       9443,\n    LeaderElection:             enableLeaderElection,\n    LeaderElectionID:           \"dcb036b8.redhat.io\",\n    LeaderElectionResourceLock: \"configmaps\",\n  })\n```  \n\nIf you want status management, add this to your CRD:\n\n```go\n  // +patchMergeKey=type\n  // +patchStrategy=merge\n  // +listType=map\n  // +listMapKey=type\n  Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"`\n}\n\nfunc (m *MyCRD) GetConditions() []metav1.Condition {\n  return m.Status.Conditions\n}\n\nfunc (m *MyCRD) SetConditions(conditions []metav1.Condition) {\n  m.Status.Conditions = conditions\n}\n```\n\nAt this point your controller is able to leverage the utility methods of this library:\n\n1. [managing CR validation](#managing-cr-validation)\n2. [managing CR initialization](#managing-cr-initialization)\n3. [managing status and error conditions](#managing-status-and-error-conditions)\n4. [managing CR finalization](#managing-cr-finalization)\n5. high-level object manipulation functions such as:\n   - createOrUpdate, createIfNotExists, deleteIfExists\n   - same functions on an array of objects\n   - go template processing of objects\n\nA full example is provided [here](./controllers/mycrd_controller.go)\n\n### Managing CR validation\n\nTo enable CR validation add this to your controller:\n\n```go\nif ok, err := r.IsValid(instance); !ok {\n return r.ManageError(ctx, instance, err)\n}\n```\n\nThe implement the following function:\n\n```go\nfunc (r *ReconcileMyCRD) IsValid(obj metav1.Object) (bool, error) {\n mycrd, ok := obj.(*examplev1alpha1.MyCRD)\n ...\n}\n```\n\n### Managing CR Initialization\n\nTo enable CR initialization, add this to your controller:\n\n```go\nif ok := r.IsInitialized(instance); !ok {\n err := r.GetClient().Update(context.TODO(), instance)\n if err != nil {\n  log.Error(err, \"unable to update instance\", \"instance\", instance)\n  return r.ManageError(ctx, instance, err)\n }\n return reconcile.Result{}, nil\n}\n```\n\nThen implement the following function:\n\n```go\nfunc (r *ReconcileMyCRD) IsInitialized(obj metav1.Object) bool {\n mycrd, ok := obj.(*examplev1alpha1.MyCRD)\n}\n```\n\n### Managing Status and Error Conditions\n\nTo update the status with success and return from the reconciliation cycle, code the following:\n\n```go\nreturn r.ManageSuccess(ctx, instance)\n```\n\nTo update the status with failure, record an event and return from the reconciliation cycle, code the following:\n\n```go\nreturn r.ManageError(ctx, instance, err)\n```\n\nnotice that this function will reschedule a reconciliation cycle with increasingly longer wait time up to six hours.\n\nThere are also variants of these calls to allow for requeuing after a given delay.\nRequeuing is handy when reconciliation depends on a cluster-external state which is not observable from within the api-server.\n\n```go\nreturn r.ManageErrorWithRequeue(ctx, instance, err, 3*time.Second)\n```\n\n```go\nreturn r.ManageSuccessWithRequeue(ctx, instance, 3*time.Second)\n```\n\nor simply using the convenience function:\n\n```go\nreturn r.ManageOutcomeWithRequeue(ctx, instance, err, 3*time.Second)\n```\n\nwhich will delegate to the error or success variant depending on `err` being `nil` or not.\n\n### Managing CR Finalization\n\nto enable CR finalization add this to your controller:\n\n```go\nif util.IsBeingDeleted(instance) {\n if !util.HasFinalizer(instance, controllerName) {\n  return reconcile.Result{}, nil\n }\n err := r.manageCleanUpLogic(instance)\n if err != nil {\n  log.Error(err, \"unable to delete instance\", \"instance\", instance)\n  return r.ManageError(ctx, instance, err)\n }\n util.RemoveFinalizer(instance, controllerName)\n err = r.GetClient().Update(context.TODO(), instance)\n if err != nil {\n  log.Error(err, \"unable to update instance\", \"instance\", instance)\n  return r.ManageError(ctx, instance, err)\n }\n return reconcile.Result{}, nil\n}\n```\n\nThen implement this method:\n\n```go\nfunc (r *ReconcileMyCRD) manageCleanUpLogic(mycrd *examplev1alpha1.MyCRD) error {\n  ...\n}\n```\n\n## Support for operators that need to enforce a set of resources to a defined state\n\nMany operators have the following logic:\n\n1. Phase 1: based on the CR and potentially additional status, a set of resources that need to exist is calculated.\n2. Phase 2: These resources are then created or updated against the master API.\n3. Phase 3: A well written operator also ensures that these resources stay in place and are not accidentally or maliciously changed by third parties.\n\nThese phases are of increasing difficulty to implement. It's also true that phase 2 and 3 can be generalized.\n\nOperator-utils offers some scaffolding to assist in writing these kinds of operators.\n\nSimilarly to the `BaseReconciler` class, we have a base type to extend called: `EnforcingReconciler`. This class extends from `BaseReconciler`, so you have all the same facilities as above.\n\nWhen initializing the EnforcingReconciler, one must chose whether watchers will be created at the cluster level or at the namespace level.\n\n- if cluster level is chosen a watch per CR and type defined in it will be created. This will require the operator to have cluster level access.\n\n- if namespace level watchers is chosen a watch per CR, type and namespace will be created. This will minimize the needed permissions, but depending on what the operator needs to do may open a very high number of connections to the API server.\n\nThe body of the reconciler function will look something like this:\n\n```golang\nvalidation...\ninitialization...\n(optional) finalization...\nPhase1 ... calculate a set of resources to be enforced -> LockedResources\n\n  err = r.UpdateLockedResources(context,instance, lockedResources, ...)\n  if err != nil {\n    log.Error(err, \"unable to update locked resources\")\n    return r.ManageError(ctx, instance, err)\n }\n\n  return r.ManageSuccess(ctx, instance)\n```\n\nthis is all you have to do for basic functionality. For more details see the [example](pkg/controller/apis/enforcingcrd/enforcingcrd_controller.go)\nthe EnforcingReconciler will do the following:\n\n1. restore the resources to the desired stated if the are changed. Notice that you can exclude paths from being considered when deciding whether to restore a resource. As set of JSON Path can be passed together with the LockedResource. It is recommended to set these paths:\n    1. `.metadata`\n    2. `.status`\n\n2. restore resources when they are deleted.\n\nThe `UpdateLockedResources` will validate the input as follows:\n\n1. the passed resource must be defined in the current apiserver\n2. the passed resource must be syntactically compliant with the OpenAPI definition of the resource defined in the server.\n3. if the passed resource is namespaced, the namespace field must be initialized.\n\nThe finalization method will look like this:\n\n```golang\nfunc (r *ReconcileEnforcingCRD) manageCleanUpLogic(instance *examplev1alpha1.EnforcingCRD) error {\n  err := r.Terminate(instance, true)\n  if err != nil {\n    log.Error(err, \"unable to terminate enforcing reconciler for\", \"instance\", instance)\n    return err\n  }\n  ... additional finalization logic ...\n  return nil\n}\n```\n\nConvenience methods are also available for when resources are templated. See the [templatedenforcingcrd](./pkgcontroller/templatedenforcingcrd/templatedenforcingcrd_controller.go) controller as an example.\n\n## Support for operators that need to enforce a set of patches\n\nFor similar reasons stated in the previous paragraphs, operators might need to enforce patches.\nA patch modifies an object created by another entity. Because in this case the CR does not own the to-be-modified object a patch must be enforced against changes made on it.\nOne must be careful not to create circular situations where an operator deletes the patch and this operator recreates the patch.\nIn some situations, a patch must be parametric on some state of the cluster. For this reason, it's possible to monitor source objects that will be used as parameters to calculate the patch.\n\nA patch is defined as follows:\n\n```golang\ntype LockedPatch struct { \n  Name             string                           `json:\"name,omitempty\"`\n  SourceObjectRefs []utilsapi.SourceObjectReference `json:\"sourceObjectRefs,omitempty\"`\n  TargetObjectRef  utilsapi.TargetObjectReference   `json:\"targetObjectRef,omitempty\"`\n  PatchType        types.PatchType                  `json:\"patchType,omitempty\"`\n  PatchTemplate    string                           `json:\"patchTemplate,omitempty\"`\n  Template         template.Template                `json:\"-\"`\n}\n```\n\nthe targetObjectRef and sourceObjectRefs are watched for changes by the reconciler.\n\ntargetObjectRef can select multiple objects, this is the logic\n\n| Namespaced Type | Namespace | Name | Selection type |\n| --- | --- | --- | --- |\n| yes | null | null | multiple selection across namespaces |\n| yes | null | not null | multiple selection across namespaces where the name corresponds to the passed name |\n| yes | not null | null | multiple selection within a namespace |\n| yes | not null | not nul | single selection |\n| no | N/A | null | multiple selection  |\n| no | N/A | not null | single selection |\n\nSelection can be further narrowed down by filtering by labels and/or annotations. The patch will be applied to all of the selected instances.\n\nName and Namespace of sourceRefObjects are interpreted as golang templates with the current target instance and the only parameter. This allows to select different source object for each target object.\n\nThe relevant part of the operator code would look like this:\n\n```golang\nvalidation...\ninitialization...\nPhase1 ... calculate a set of patches to be enforced -> LockedPatches\n\n  err = r.UpdateLockedResources(context, instance, ..., lockedPatches...)\n  if err != nil {\n    log.Error(err, \"unable to update locked resources\")\n    return r.ManageError(ctx, instance, err)\n }\n\n  return r.ManageSuccess(ctx, instance)\n```\n\nThe `UpdateLockedResources` will validate the input as follows:\n\n1. the passed patch target/source `ObjectRef` resource must be defined in the current apiserver\n2. if the passed patch target/source `ObjectRef` resources are namespaced the corresponding namespace field must be initialized.\n3. the ID must have a not null and unique value in the array of the passed patches.\n\nPatches cannot be undone so there is no need to manage a finalizer.\n\n[Here](./pkg/controller/enforcingpatch/enforcingpatch_controller.go) you can find an example of how to implement an operator with this the ability to enforce patches.\n\n## Support for operators that need dynamic creation of locked resources using templates\n\nOperators may also need to leverage locked resources created dynamically through templates. This can be done using [go templates](https://golang.org/pkg/text/template/) and leveraging the `GetLockedResourcesFromTemplates` function.\n\n```golang\nlockedResources, err := r.GetLockedResourcesFromTemplates(templates..., params...)\nif err != nil {\n  log.Error(err, \"unable to process templates with param\")\n  return err\n}\n```\n\nThe `GetLockedResourcesFromTemplates` will validate the input as follows:\n\n1. check that the passed template is valid\n2. format the template using the properties of the passed object in the params parameter\n3. create an array of `LockedResource` objects based on parsed template\n\nThe example below shows how templating can be used to reference the name of the resource passed as the parameter and use it as a property in the creation of the `LockedResource`.\n\n```golang\nobjectTemplate: |\n  apiVersion: v1\n  kind: Namespace\n  metadata:\n    name: {{ .Name }}\n```\n\nThis functionality can leverage advanced features of go templating, such as loops, to generate more than one object following a set pattern. The below example will create an array of namespace `LockedResources` using the title of any key where the associated value matches the text *devteam* in the key/value pair of the `Labels` property of the resource passed in the params parameter.\n\n```golang\nobjectTemplate: |\n  {{range $key, $value := $.Labels}}\n    {{if eq $value \"devteam\"}}\n      - apiVersion: v1\n        kind: Namespace\n        metadata:\n          name: {{ $key }}\n    {{end}}\n  {{end}}\n```\n\n## Support for operators that need advanced templating functionality\n\nOperators may need to utilize advanced templating functions not found in the base go templating library. This advanced template functionality matches the same available in the popular k8s management tool [Helm](https://helm.sh/). `LockedPatch` templates uses this functionality by default. To utilize these features when using `LockedResources` the following function is required,\n\n```golang\nlockedResources, err := r.GetLockedResourcesFromTemplatesWithRestConfig(templates..., rest.Config..., params...)\nif err != nil {\n  log.Error(err, \"unable to process templates with param\")\n  return err\n}\n```  \n\n## Deployment\n\n### Deploying with Helm\n\nHere are the instructions to install the latest release with Helm.\n\n```shell\noc new-project operator-utils\nhelm repo add operator-utils https://redhat-cop.github.io/operator-utils\nhelm repo update\nhelm install operator-utils operator-utils/operator-utils\n```\n\nThis can later be updated with the following commands:\n\n```shell\nhelm repo update\nhelm upgrade operator-utils operator-utils/operator-utils\n```\n\n## Development\n\n## Running the operator locally\n\n```shell\nmake install\noc new-project operator-utils-operator-local\nkustomize build ./config/local-development | oc apply -f - -n operator-utils-operator-local\nexport token=$(oc serviceaccounts get-token 'operator-utils-operator-controller-manager' -n operator-utils-operator-local)\noc login --token ${token}\nmake run ENABLE_WEBHOOKS=false\n```\n\n### testing\n\nPatches\n\n```shell\noc new-project patch-test\noc create sa test -n patch-test\noc adm policy add-cluster-role-to-user cluster-admin -z default -n patch-test\noc apply -f ./test/enforcing-patch.yaml -n patch-test\noc apply -f ./test/enforcing-patch-multiple.yaml -n patch-test\noc apply -f ./test/enforcing-patch-multiple-cluster-level.yaml -n patch-test\n```\n\n## Building/Pushing the operator image\n\n```shell\nexport repo=raffaelespazzoli #replace with yours\ndocker login quay.io/$repo\nmake docker-build IMG=quay.io/$repo/operator-utils:latest\nmake docker-push IMG=quay.io/$repo/operator-utils:latest\n```\n\n## Deploy to OLM via bundle\n\n```shell\nmake manifests\nmake bundle IMG=quay.io/$repo/operator-utils:latest\noperator-sdk bundle validate ./bundle --select-optional name=operatorhub\nmake bundle-build BUNDLE_IMG=quay.io/$repo/operator-utils-bundle:latest\ndocker push quay.io/$repo/operator-utils-bundle:latest\noperator-sdk bundle validate quay.io/$repo/operator-utils-bundle:latest --select-optional name=operatorhub\noc new-project operator-utils\noc label namespace operator-utils openshift.io/cluster-monitoring=\"true\"\noperator-sdk cleanup operator-utils -n operator-utils\noperator-sdk run bundle --install-mode AllNamespaces -n operator-utils quay.io/$repo/operator-utils-bundle:latest\n```\n\n## Releasing\n\n```shell\ngit tag -a \"<tagname>\" -m \"<commit message>\"\ngit push upstream <tagname>\n```\n\nIf you need to remove a release:\n\n```shell\ngit tag -d <tagname>\ngit push upstream --delete <tagname>\n```\n\nIf you need to \"move\" a release to the current main\n\n```shell\ngit tag -f <tagname>\ngit push upstream -f <tagname>\n```\n\n### Cleaning up\n\n```shell\noperator-sdk cleanup operator-utils -n operator-utils\noc delete operatorgroup operator-sdk-og\noc delete catalogsource operator-utils-catalog\n```\n"
  },
  {
    "path": "api/v1alpha1/enforcingcrd_types.go",
    "content": "/*\n\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// EnforcingCRDSpec defines the desired state of EnforcingCRD\ntype EnforcingCRDSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"operator-sdk generate k8s\" to regenerate code after modifying this file\n\t// Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html\n\t// Resources is a list of resource manifests that should be locked into the specified configuration\n\t// +kubebuilder:validation:Optional\n\t// +listType=atomic\n\tResources []LockedResource `json:\"resources,omitempty\"`\n}\n\n// EnforcingCRDStatus defines the observed state of EnforcingCRD\ntype EnforcingCRDStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"operator-sdk generate k8s\" to regenerate code after modifying this file\n\t// Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html\n\t// +kubebuilder:validation:Optional\n\tEnforcingReconcileStatus `json:\",inline,omitempty\"`\n}\n\nfunc (m *EnforcingCRD) GetEnforcingReconcileStatus() EnforcingReconcileStatus {\n\treturn m.Status.EnforcingReconcileStatus\n}\n\nfunc (m *EnforcingCRD) SetEnforcingReconcileStatus(reconcileStatus EnforcingReconcileStatus) {\n\tm.Status.EnforcingReconcileStatus = reconcileStatus\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n\n// EnforcingCRD is the Schema for the enforcingcrds API\ntype EnforcingCRD struct {\n\tmetav1.TypeMeta   `json:\",inline\"`\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tSpec   EnforcingCRDSpec   `json:\"spec,omitempty\"`\n\tStatus EnforcingCRDStatus `json:\"status,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n\n// EnforcingCRDList contains a list of EnforcingCRD\ntype EnforcingCRDList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitempty\"`\n\tItems           []EnforcingCRD `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&EnforcingCRD{}, &EnforcingCRDList{})\n}\n"
  },
  {
    "path": "api/v1alpha1/enforcingpatch_types.go",
    "content": "/*\n\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// EnforcingPatchSpec defines the desired state of EnforcingPatch\ntype EnforcingPatchSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"operator-sdk generate k8s\" to regenerate code after modifying this file\n\t// Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html\n\n\t// Patches is a list of pacthes that should be encforced at runtime.\n\t// +kubebuilder:validation:Optional\n\tPatches map[string]PatchSpec `json:\"patches,omitempty\"`\n}\n\n// EnforcingPatchStatus defines the observed state of EnforcingPatch\ntype EnforcingPatchStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"operator-sdk generate k8s\" to regenerate code after modifying this file\n\t// Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html\n\tEnforcingReconcileStatus `json:\",inline,omitempty\"`\n}\n\nfunc (m *EnforcingPatch) GetEnforcingReconcileStatus() EnforcingReconcileStatus {\n\treturn m.Status.EnforcingReconcileStatus\n}\n\nfunc (m *EnforcingPatch) SetEnforcingReconcileStatus(reconcileStatus EnforcingReconcileStatus) {\n\tm.Status.EnforcingReconcileStatus = reconcileStatus\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n\n// EnforcingPatch is the Schema for the enforcingpatches API\ntype EnforcingPatch struct {\n\tmetav1.TypeMeta   `json:\",inline\"`\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tSpec   EnforcingPatchSpec   `json:\"spec,omitempty\"`\n\tStatus EnforcingPatchStatus `json:\"status,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n\n// EnforcingPatchList contains a list of EnforcingPatch\ntype EnforcingPatchList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitempty\"`\n\tItems           []EnforcingPatch `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&EnforcingPatch{}, &EnforcingPatchList{})\n}\n"
  },
  {
    "path": "api/v1alpha1/enforcingreconcilerstatus.go",
    "content": "package v1alpha1\n\nimport metav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\n// +listType=map\n// +listMapKey=type\ntype Conditions []metav1.Condition\n\n// +mapType=granular\ntype ConditionMap map[string]Conditions\n\n// EnforcingReconcileStatus represents the status of the last reconcile cycle. It's used to communicate success or failure and the error message\ntype EnforcingReconcileStatus struct {\n\n\t// ReconcileStatus this is the general status of the main reconciler\n\t// +kubebuilder:validation:Optional\n\t// +listType=map\n\t// +listMapKey=type\n\tConditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"`\n\n\t//LockedResourceStatuses contains the reconcile status for each of the managed resources\n\t// +kubebuilder:validation:Optional\n\tLockedResourceStatuses map[string]Conditions `json:\"lockedResourceStatuses,omitempty\"`\n\n\t//LockedResourceStatuses contains the reconcile status for each of the managed resources\n\t// +kubebuilder:validation:Optional\n\tLockedPatchStatuses map[string]ConditionMap `json:\"lockedPatchStatuses,omitempty\"`\n}\n\n// EnforcingReconcileStatusAware is an interfce that must be implemented by a CRD type that has been enabled with ReconcileStatus, it can then benefit of a series of utility methods.\n// +kubebuilder:object:generate:=false\ntype EnforcingReconcileStatusAware interface {\n\tGetEnforcingReconcileStatus() EnforcingReconcileStatus\n\tSetEnforcingReconcileStatus(enforcingReconcileStatus EnforcingReconcileStatus)\n}\n"
  },
  {
    "path": "api/v1alpha1/groupversion_info.go",
    "content": "/*\n\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Package v1alpha1 contains API Schema definitions for the operator-utils v1alpha1 API group\n// +kubebuilder:object:generate=true\n// +groupName=operator-utils.example.io\npackage v1alpha1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"sigs.k8s.io/controller-runtime/pkg/scheme\"\n)\n\nvar (\n\t// GroupVersion is group version used to register these objects\n\tGroupVersion = schema.GroupVersion{Group: \"operator-utils.example.io\", Version: \"v1alpha1\"}\n\n\t// SchemeBuilder is used to add go types to the GroupVersionKind scheme\n\tSchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}\n\n\t// AddToScheme adds the types in this group-version to the given scheme.\n\tAddToScheme = SchemeBuilder.AddToScheme\n)\n"
  },
  {
    "path": "api/v1alpha1/lockedpatch.go",
    "content": "package v1alpha1\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"errors\"\n\t\"text/template\"\n\n\t\"github.com/redhat-cop/operator-utils/pkg/util/discoveryclient\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/dynamicclient\"\n\tutiltemplates \"github.com/redhat-cop/operator-utils/pkg/util/templates\"\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/labels\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/client-go/dynamic\"\n\t\"k8s.io/client-go/rest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log\"\n)\n\n// Patch describes a patch to be enforced at runtime\n// +k8s:openapi-gen=true\ntype PatchSpec struct {\n\t//Name represents a unique name for this patch, it has no particular effect, except for internal bookeeping\n\n\t// SourceObjectRefs is an arrays of refereces to source objects that will be used as input for the template processing. These refernces must resolve to single instance. The resolution rule is as follows (+ present, - absent):\n\t// the King and APIVersion field are mandatory\n\t// +Namespace +Name: resolves to object <Namespace>/<Name>\n\t// +Namespace -Name: results in an error\n\t// -Namespace +Name: resolves to cluster-level object <Name>. If Kind is namespaced, this results in an error.\n\t// -Namespace -Name: results in an error\n\t// Name manespaces Namespace are evaluated as golang templates with the input of the template being the target object. When selecting multiple target, this allows for having specific source objects for each target.\n\t// ResourceVersion and UID are always ignored\n\t// If FieldPath is specified, the restuned object is calculated from the path, so for example if FieldPath=.spec, the only the spec portion of the object is returned.\n\t// The target object is always added as element zero of the array of the SourceObjectRefs\n\t// +kubebuilder:validation:Optional\n\t// +listType=atomic\n\tSourceObjectRefs []SourceObjectReference `json:\"sourceObjectRefs,omitempty\"`\n\n\t// TargetObjectRef is a reference to the object to which the pacth should be applied.\n\t// the King and APIVersion field are mandatory\n\t// the Name and Namespace field have the following meaning (+ present, - absent)\n\t// +Namespace +Name: apply the patch to the object: <Namespace>/<Name>\n\t// +Namespace -Name: apply the patch to all of the objects in <Namespace>\n\t// -Namespace +Name: apply the patch to the cluster-level object <Name>. If Kind is namespaced, this results in an error.\n\t// -Namespace -Name: if the kind is namespaced apply the patch to all of the objects in all of the namespaces. If the kind is not namespaced, apply the patch to all of the cluster level objects.\n\t// The lable selector can be used to further filter the selected objects.\n\t// +kubebuilder:validation:Required\n\tTargetObjectRef TargetObjectReference `json:\"targetObjectRef,omitempty\"`\n\n\t// PatchType is the type of patch to be applied, one of \"application/json-patch+json\"'\"application/merge-patch+json\",\"application/strategic-merge-patch+json\",\"application/apply-patch+yaml\"\n\t// +kubebuilder:validation:Required\n\t// +kubebuilder:validation:Enum=\"application/json-patch+json\";\"application/merge-patch+json\";\"application/strategic-merge-patch+json\";\"application/apply-patch+yaml\"\n\t// default:=\"application/strategic-merge-patch+json\"\n\tPatchType types.PatchType `json:\"patchType,omitempty\"`\n\n\t// PatchTemplate is a go template that will be resolved using the SourceObjectRefs as parameters. The result must be a valid patch based on the pacth type and the target object.\n\t// +kubebuilder:validation:Required\n\tPatchTemplate string `json:\"patchTemplate,omitempty\"`\n}\n\ntype TargetObjectReference struct {\n\t// API version of the referent.\n\t// +kubebuilder:validation:Required\n\tAPIVersion string `json:\"apiVersion,omitempty\" protobuf:\"bytes,5,opt,name=apiVersion\"`\n\n\t// Kind of the referent.\n\t// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n\t// +kubebuilder:validation:Required\n\tKind string `json:\"kind,omitempty\" protobuf:\"bytes,1,opt,name=kind\"`\n\n\t// Namespace of the referent.\n\t// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/\n\t// +kubebuilder:validation:Optional\n\tNamespace string `json:\"namespace,omitempty\" protobuf:\"bytes,2,opt,name=namespace\"`\n\n\t// Name of the referent.\n\t// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n\t// +kubebuilder:validation:Optional\n\tName string `json:\"name,omitempty\" protobuf:\"bytes,3,opt,name=name\"`\n\n\t// LabelSelector selects objects by label\n\t// +kubebuilder:validation:Optional\n\tLabelSelector *metav1.LabelSelector `json:\"labelSelector,omitempty\"`\n\n\t// AnnotationSelector selects objects by label\n\tAnnotationSelector *metav1.LabelSelector `json:\"annotationSelector,omitempty\"`\n\n\t//apiResource caches apiResource for this targetReference\n\tapiResource *metav1.APIResource `json:\"-\"`\n}\n\nfunc (t *TargetObjectReference) getAPIReourceForGVK(context context.Context) (*metav1.APIResource, bool, error) {\n\tif t.apiResource != nil {\n\t\treturn t.apiResource, true, nil\n\t}\n\tapiresource, found, err := discoveryclient.GetAPIResourceForGVK(context, schema.FromAPIVersionAndKind(t.APIVersion, t.Kind))\n\tif err != nil && found {\n\t\tt.apiResource = apiresource\n\t}\n\treturn apiresource, found, err\n}\n\nfunc (t *TargetObjectReference) getDynamicClient(context context.Context) (dynamic.ResourceInterface, error) {\n\tlog := log.FromContext(context)\n\t_, namespacedSelection, err := t.IsSelectingMultipleInstances(context)\n\tif err != nil {\n\t\tlog.Error(err, \"unable to determine if the target reference is selecting multiple instance\", \"targetReference\", t)\n\t\treturn nil, err\n\t}\n\tvar ri dynamic.ResourceInterface\n\tnri, namespaced, err := dynamicclient.GetDynamicClientForGVK(context, schema.FromAPIVersionAndKind(t.APIVersion, t.Kind))\n\tif err != nil {\n\t\tlog.Error(err, \"unable to get dynamicClient on \", \"gvk\", schema.FromAPIVersionAndKind(t.APIVersion, t.Kind))\n\t\treturn nil, err\n\t}\n\tif namespaced && namespacedSelection {\n\t\tri = nri.Namespace(t.Namespace)\n\t} else {\n\t\tri = nri\n\t}\n\treturn ri, nil\n}\n\nfunc (t *TargetObjectReference) GetReferencedObjectWithName(context context.Context, namespacedName types.NamespacedName) (*unstructured.Unstructured, error) {\n\tlog := log.FromContext(context)\n\tmultiple, _, err := t.IsSelectingMultipleInstances(context)\n\tif err != nil {\n\t\tlog.Error(err, \"unable to determine if the target reference is selecting multiple instance\", \"targetReference\", t)\n\t\treturn nil, err\n\t}\n\tif !multiple {\n\t\treturn t.GetReferencedObject(context)\n\t}\n\ttargetCopy := t.DeepCopy()\n\ttargetCopy.Name = namespacedName.Name\n\tnamespaced, err := t.IsNamespaced(context)\n\tif err != nil {\n\t\tlog.Error(err, \"unable to determine if the target reference is namespaced\", \"targetReference\", t)\n\t\treturn nil, err\n\t}\n\tif namespaced {\n\t\ttargetCopy.Namespace = namespacedName.Namespace\n\t}\n\tclient, err := targetCopy.getDynamicClient(context)\n\tif err != nil {\n\t\tlog.Error(err, \"unable to get dynamic client with\", \"targetReference\", targetCopy)\n\t\treturn nil, err\n\t}\n\tobj, err := client.Get(context, targetCopy.Name, metav1.GetOptions{})\n\tif err != nil {\n\t\tlog.Error(err, \"unable to get referenced object\", \"targetReference\", targetCopy)\n\t\treturn nil, err\n\t}\n\treturn obj, nil\n}\n\nfunc (t *TargetObjectReference) GetReferencedObject(context context.Context) (*unstructured.Unstructured, error) {\n\tlog := log.FromContext(context)\n\tmultiple, _, err := t.IsSelectingMultipleInstances(context)\n\tif err != nil {\n\t\tlog.Error(err, \"unable to determine if the target reference is selecting multiple instance\", \"targetReference\", t)\n\t\treturn nil, err\n\t}\n\tif multiple {\n\t\treturn nil, errors.New(\"cannot call this method on a target that selects multiple instances\")\n\t}\n\tdclient, err := t.getDynamicClient(context)\n\tif err != nil {\n\t\tlog.Error(err, \"unable to get dynamic client on\", \"targetReference\", t)\n\t\treturn nil, err\n\t}\n\tobj, err := dclient.Get(context, t.Name, metav1.GetOptions{})\n\tif err != nil {\n\t\tlog.Error(err, \"unable to get referenced \", \"object\", t)\n\t\treturn nil, err\n\t}\n\treturn obj, nil\n}\n\nfunc (t *TargetObjectReference) GetReferencedObjects(context context.Context) ([]unstructured.Unstructured, error) {\n\tlog := log.FromContext(context)\n\tmultiple, _, err := t.IsSelectingMultipleInstances(context)\n\tif err != nil {\n\t\tlog.Error(err, \"unable to determine if the target reference is selecting multiple instance\", \"targetReference\", t)\n\t\treturn nil, err\n\t}\n\tif !multiple {\n\t\treturn nil, errors.New(\"cannot call this method on a target that does not select multiple instances\")\n\t}\n\tdclient, err := t.getDynamicClient(context)\n\tif err != nil {\n\t\tlog.Error(err, \"unable to get dynamic client on\", \"targetReference\", t)\n\t\treturn nil, err\n\t}\n\n\tlabelSelector, err := metav1.LabelSelectorAsSelector(t.LabelSelector)\n\tif err != nil {\n\t\tlog.Error(err, \"unable to process \", \"labelSelector\", t.LabelSelector)\n\t\treturn nil, err\n\t}\n\tobjList, err := dclient.List(context, metav1.ListOptions{\n\t\tLabelSelector: labelSelector.String(),\n\t})\n\tif err != nil {\n\t\tlog.Error(err, \"unable to list referenced \", \"objects\", t)\n\t\treturn nil, err\n\t}\n\tvar annotatonSelector labels.Selector\n\tif t.AnnotationSelector != nil {\n\t\tannotatonSelector, err = metav1.LabelSelectorAsSelector(t.AnnotationSelector)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t} else {\n\t\tannotatonSelector = labels.Everything()\n\t}\n\t//filter by annotation\n\tannotationFilteredList := []unstructured.Unstructured{}\n\tfor i := range objList.Items {\n\t\tif annotatonSelector.Matches(labels.Set(objList.Items[i].GetAnnotations())) {\n\t\t\tannotationFilteredList = append(annotationFilteredList, objList.Items[i])\n\t\t}\n\t}\n\t//filter by name\n\tif t.Name != \"\" {\n\t\tfilteredList := []unstructured.Unstructured{}\n\t\tfor i := range annotationFilteredList {\n\t\t\tif t.Name == annotationFilteredList[i].GetName() {\n\t\t\t\tfilteredList = append(filteredList, annotationFilteredList[i])\n\t\t\t}\n\t\t}\n\t\treturn filteredList, nil\n\t}\n\treturn objList.Items, nil\n}\n\nfunc (t *TargetObjectReference) IsNamespaced(context context.Context) (bool, error) {\n\tapiresource, found, err := t.getAPIReourceForGVK(context)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tif !found {\n\t\treturn false, errors.New(\"resource not found\" + schema.FromAPIVersionAndKind(t.APIVersion, t.Kind).String())\n\t}\n\treturn apiresource.Namespaced, nil\n}\n\n// IsSelectingMultipleInstances is a helper function to determine whether this targetObjectReference selects one or multiple instance.\nfunc (t *TargetObjectReference) IsSelectingMultipleInstances(context context.Context) (multiple bool, namespacedSelection bool, err error) {\n\tlog := log.FromContext(context)\n\tnamespaced, err := t.IsNamespaced(context)\n\tif err != nil {\n\t\tlog.Error(err, \"Unable to determine if targetObjectReference is namespaced\", \"TargetObjectReference\", t)\n\t\treturn false, false, err\n\t}\n\tif namespaced {\n\t\tif t.Namespace == \"\" {\n\t\t\treturn true, false, nil\n\t\t} else {\n\t\t\tif t.Name == \"\" {\n\t\t\t\treturn true, true, nil\n\t\t\t} else {\n\t\t\t\treturn false, true, nil\n\t\t\t}\n\t\t}\n\t} else {\n\t\treturn t.Name == \"\", false, nil\n\t}\n}\n\n// Selects returns whether the passed object is selected by the current target reference\n// requires context with log and restConfig\nfunc (t *TargetObjectReference) Selects(context context.Context, obj client.Object) (bool, error) {\n\tlog := log.FromContext(context)\n\tif apiversion, kind := obj.GetObjectKind().GroupVersionKind().ToAPIVersionAndKind(); t.Kind != kind || t.APIVersion != apiversion {\n\t\treturn false, nil\n\t}\n\tvar labelSelector labels.Selector\n\tvar annotatonSelector labels.Selector\n\tvar err error\n\tif t.LabelSelector != nil {\n\t\tlabelSelector, err = metav1.LabelSelectorAsSelector(t.LabelSelector)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t} else {\n\t\tlabelSelector = labels.Everything()\n\t}\n\tif t.AnnotationSelector != nil {\n\t\tannotatonSelector, err = metav1.LabelSelectorAsSelector(t.AnnotationSelector)\n\t\tif err != nil {\n\t\t\treturn false, err\n\t\t}\n\t} else {\n\t\tannotatonSelector = labels.Everything()\n\t}\n\tnamespaced, err := discoveryclient.IsGVKNamespaced(context, obj.GetObjectKind().GroupVersionKind())\n\tif err != nil {\n\t\tlog.Error(err, \"Unable to determine if GVK is namespaced\", \"GVK\", obj.GetObjectKind().GroupVersionKind())\n\t\treturn false, err\n\t}\n\tif namespaced {\n\t\tif t.Namespace != \"\" {\n\t\t\t//we are selecting within a namespace\n\t\t\tif t.Namespace != obj.GetNamespace() {\n\t\t\t\treturn false, nil\n\t\t\t}\n\t\t}\n\t\tif t.Name != \"\" {\n\t\t\t// we are matching on name\n\t\t\treturn t.Name == obj.GetName(), nil\n\t\t} else {\n\t\t\t// we select via selectors\n\t\t\treturn labelSelector.Matches(labels.Set(obj.GetLabels())) && annotatonSelector.Matches(labels.Set(obj.GetAnnotations())), nil\n\t\t}\n\t} else {\n\t\t//cluster object, we ignore namespace\n\t\tif t.Name != \"\" {\n\t\t\t// we select via name\n\t\t\treturn t.Name == obj.GetName(), nil\n\n\t\t} else {\n\t\t\t// we select via selectors\n\t\t\treturn labelSelector.Matches(labels.Set(obj.GetLabels())) && annotatonSelector.Matches(labels.Set(obj.GetAnnotations())), nil\n\t\t}\n\t}\n}\n\n// GetNameAndNamespace processes the templates for Name and Namespace of the sourceObjectReference\n// requires context with log and restConfig\nfunc (s *SourceObjectReference) GetNameAndNamespace(context context.Context, target *unstructured.Unstructured) (name string, namespace string, err error) {\n\tlog := log.FromContext(context)\n\tname, err = processTemplate(context, s.Name, target.UnstructuredContent())\n\tif err != nil {\n\t\tlog.Error(err, \"unable to process template for\", \"name\", s.Name)\n\t\treturn \"\", \"\", err\n\t}\n\tnamespace, err = processTemplate(context, s.Namespace, target.UnstructuredContent())\n\tif err != nil {\n\t\tlog.Error(err, \"unable to process template for\", \"namespace\", s.Name)\n\t\treturn \"\", \"\", err\n\t}\n\treturn\n}\n\nfunc (t *SourceObjectReference) getAPIReourceForGVK(context context.Context) (*metav1.APIResource, bool, error) {\n\tif t.apiResource != nil {\n\t\treturn t.apiResource, true, nil\n\t}\n\tapiresource, found, err := discoveryclient.GetAPIResourceForGVK(context, schema.FromAPIVersionAndKind(t.APIVersion, t.Kind))\n\tif err != nil && found {\n\t\tt.apiResource = apiresource\n\t}\n\treturn apiresource, found, err\n}\n\nfunc (t *SourceObjectReference) getDynamicClient(context context.Context) (dynamic.ResourceInterface, error) {\n\tlog := log.FromContext(context)\n\n\tvar ri dynamic.ResourceInterface\n\tnri, namespaced, err := dynamicclient.GetDynamicClientForGVK(context, schema.FromAPIVersionAndKind(t.APIVersion, t.Kind))\n\tif err != nil {\n\t\tlog.Error(err, \"unable to get dynamicClient on \", \"gvk\", schema.FromAPIVersionAndKind(t.APIVersion, t.Kind))\n\t\treturn nil, err\n\t}\n\tif namespaced {\n\t\tri = nri.Namespace(t.Namespace)\n\t} else {\n\t\tri = nri\n\t}\n\treturn ri, nil\n}\n\nfunc (s *SourceObjectReference) GetReferencedObject(context context.Context, target *unstructured.Unstructured) (*unstructured.Unstructured, error) {\n\tlog := log.FromContext(context)\n\tname, namespace, err := s.GetNameAndNamespace(context, target)\n\tif err != nil {\n\t\tlog.Error(err, \"unable to get name and namespaces on \", \"SourceObjectReference\", s, \"with target\", target)\n\t\treturn nil, err\n\t}\n\tsourceCopy := s.DeepCopy()\n\tsourceCopy.Name = name\n\tsourceCopy.Namespace = namespace\n\tclient, err := sourceCopy.getDynamicClient(context)\n\tif err != nil {\n\t\tlog.Error(err, \"unable to get dynamic client for \", \"source\", sourceCopy)\n\t\treturn nil, err\n\t}\n\tobj, err := client.Get(context, name, metav1.GetOptions{})\n\tif err != nil {\n\t\tlog.Error(err, \"unable to get referenced object \", \"sourceCopy\", sourceCopy)\n\t\treturn nil, err\n\t}\n\treturn obj, nil\n}\n\nfunc processTemplate(context context.Context, templateString string, param interface{}) (string, error) {\n\tlog := log.FromContext(context)\n\trestConfig := context.Value(\"restConfig\").(*rest.Config)\n\ttemplate, err := template.New(templateString).Funcs(utiltemplates.AdvancedTemplateFuncMap(restConfig, log)).Parse(templateString)\n\tif err != nil {\n\t\tlog.Error(err, \"unable to parse\", \"template\", templateString)\n\t\treturn \"\", err\n\t}\n\tvar b bytes.Buffer\n\terr = template.Execute(&b, param)\n\tif err != nil {\n\t\tlog.Error(err, \"unable to process\", \"template\", templateString, \"with param\", param)\n\t\treturn \"\", err\n\t}\n\treturn b.String(), nil\n}\n\ntype SourceObjectReference struct {\n\t// API version of the referent.\n\t// +kubebuilder:validation:Required\n\tAPIVersion string `json:\"apiVersion,omitempty\" protobuf:\"bytes,5,opt,name=apiVersion\"`\n\n\t// Kind of the referent.\n\t// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds\n\t// +kubebuilder:validation:Required\n\tKind string `json:\"kind,omitempty\" protobuf:\"bytes,1,opt,name=kind\"`\n\n\t// Namespace of the referent.\n\t// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/\n\t// +kubebuilder:validation:Optional\n\tNamespace string `json:\"namespace,omitempty\" protobuf:\"bytes,2,opt,name=namespace\"`\n\n\t// Name of the referent.\n\t// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names\n\t// +kubebuilder:validation:Optional\n\tName string `json:\"name,omitempty\" protobuf:\"bytes,3,opt,name=name\"`\n\n\t// If referring to a piece of an object instead of an entire object, this string\n\t// should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2].\n\t// For example, if the object reference is to a container within a pod, this would take on a value like:\n\t// \"spec.containers{name}\" (where \"name\" refers to the name of the container that triggered\n\t// the event) or if no container name is specified \"spec.containers[2]\" (container with\n\t// index 2 in this pod). This syntax is chosen only to have some well-defined way of\n\t// referencing a part of an object.\n\t// +kubebuilder:validation:Optional\n\tFieldPath string `json:\"fieldPath,omitempty\" protobuf:\"bytes,7,opt,name=fieldPath\"`\n\n\t//apiResource caches apiResource for this targetReference\n\tapiResource *metav1.APIResource `json:\"-\"`\n}\n"
  },
  {
    "path": "api/v1alpha1/lockedresource.go",
    "content": "package v1alpha1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// LockedResource represents a resource to be enforced in a LockedResourceController and can be used in a API specification\n// +k8s:openapi-gen=true\ntype LockedResource struct {\n\n\t// Object is a yaml representation of an API resource\n\t// +kubebuilder:validation:Required\n\tObject runtime.RawExtension `json:\"object\"`\n\n\t// ExludedPaths are a set of json paths that need not be considered by the LockedResourceReconciler\n\t// +kubebuilder:validation:Optional\n\t// +listType=set\n\tExcludedPaths []string `json:\"excludedPaths,omitempty\"`\n}\n\n// LockedResourceTemplate represents a resource template in go language to be enforced in a LockedResourceController and can be used in a API specification\n// +k8s:openapi-gen=true\ntype LockedResourceTemplate struct {\n\n\t// ObjectTemplate is a goland template. Whne processed, it must resolve to a yaml representation of an API resource\n\t// +kubebuilder:validation:Required\n\tObjectTemplate string `json:\"objectTemplate\"`\n\n\t// ExludedPaths are a set of json paths that need not be considered by the LockedResourceReconciler\n\t// +kubebuilder:validation:Optional\n\t// +listType=set\n\tExcludedPaths []string `json:\"excludedPaths,omitempty\"`\n}\n"
  },
  {
    "path": "api/v1alpha1/mycrd_types.go",
    "content": "/*\n\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// MyCRDSpec defines the desired state of MyCRD\ntype MyCRDSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"operator-sdk generate k8s\" to regenerate code after modifying this file\n\t// Add custom validation using kubebuilder tags: https://book.kubebuilder.io/beyond_basics/generating_crd.html\n\tInitialized bool `json:\"initialized\"`\n\tValid       bool `json:\"valid\"`\n\tError       bool `json:\"error\"`\n}\n\n// MyCRDStatus defines the observed state of MyCRD\ntype MyCRDStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"operator-sdk generate k8s\" to regenerate code after modifying this file\n\t// Add custom validation using kubebuilder tags: https://book.kubebuilder.io/beyond_basics/generating_crd.html\n\t//     // +patchMergeKey=type\n\t//     // +patchStrategy=merge\n\t//     // +listType=map\n\t//     // +listMapKey=type\n\t//     Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\t// +patchMergeKey=type\n\t// +patchStrategy=merge\n\t// +listType=map\n\t// +listMapKey=type\n\tConditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"`\n}\n\nfunc (m *MyCRD) GetConditions() []metav1.Condition {\n\treturn m.Status.Conditions\n}\n\nfunc (m *MyCRD) SetConditions(conditions []metav1.Condition) {\n\tm.Status.Conditions = conditions\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n\n// MyCRD is the Schema for the mycrds API\ntype MyCRD struct {\n\tmetav1.TypeMeta   `json:\",inline\"`\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tSpec   MyCRDSpec   `json:\"spec,omitempty\"`\n\tStatus MyCRDStatus `json:\"status,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n\n// MyCRDList contains a list of MyCRD\ntype MyCRDList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitempty\"`\n\tItems           []MyCRD `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&MyCRD{}, &MyCRDList{})\n}\n"
  },
  {
    "path": "api/v1alpha1/templatedenforcingcrd_types.go",
    "content": "/*\n\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage v1alpha1\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!\n// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.\n\n// TemplatedEnforcingCRDSpec defines the desired state of TemplatedEnforcingCRD\ntype TemplatedEnforcingCRDSpec struct {\n\t// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n\t// Important: Run \"operator-sdk generate k8s\" to regenerate code after modifying this file\n\t// Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html\n\t// +kubebuilder:validation:Optional\n\t// +listType=atomic\n\tTemplates []LockedResourceTemplate `json:\"templates,omitempty\"`\n}\n\n// TemplatedEnforcingCRDStatus defines the observed state of TemplatedEnforcingCRD\ntype TemplatedEnforcingCRDStatus struct {\n\t// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster\n\t// Important: Run \"operator-sdk generate k8s\" to regenerate code after modifying this file\n\t// Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html\n\t// +kubebuilder:validation:Optional\n\tEnforcingReconcileStatus `json:\",inline,omitempty\"`\n}\n\nfunc (m *TemplatedEnforcingCRD) GetEnforcingReconcileStatus() EnforcingReconcileStatus {\n\treturn m.Status.EnforcingReconcileStatus\n}\n\nfunc (m *TemplatedEnforcingCRD) SetEnforcingReconcileStatus(reconcileStatus EnforcingReconcileStatus) {\n\tm.Status.EnforcingReconcileStatus = reconcileStatus\n}\n\n// +kubebuilder:object:root=true\n// +kubebuilder:subresource:status\n\n// TemplatedEnforcingCRD is the Schema for the templatedenforcingcrds API\ntype TemplatedEnforcingCRD struct {\n\tmetav1.TypeMeta   `json:\",inline\"`\n\tmetav1.ObjectMeta `json:\"metadata,omitempty\"`\n\n\tSpec   TemplatedEnforcingCRDSpec   `json:\"spec,omitempty\"`\n\tStatus TemplatedEnforcingCRDStatus `json:\"status,omitempty\"`\n}\n\n// +kubebuilder:object:root=true\n\n// TemplatedEnforcingCRDList contains a list of TemplatedEnforcingCRD\ntype TemplatedEnforcingCRDList struct {\n\tmetav1.TypeMeta `json:\",inline\"`\n\tmetav1.ListMeta `json:\"metadata,omitempty\"`\n\tItems           []TemplatedEnforcingCRD `json:\"items\"`\n}\n\nfunc init() {\n\tSchemeBuilder.Register(&TemplatedEnforcingCRD{}, &TemplatedEnforcingCRDList{})\n}\n"
  },
  {
    "path": "api/v1alpha1/zz_generated.deepcopy.go",
    "content": "//go:build !ignore_autogenerated\n// +build !ignore_autogenerated\n\n/*\n\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n// Code generated by controller-gen. DO NOT EDIT.\n\npackage v1alpha1\n\nimport (\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n)\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in ConditionMap) DeepCopyInto(out *ConditionMap) {\n\t{\n\t\tin := &in\n\t\t*out = make(ConditionMap, len(*in))\n\t\tfor key, val := range *in {\n\t\t\tvar outVal []v1.Condition\n\t\t\tif val == nil {\n\t\t\t\t(*out)[key] = nil\n\t\t\t} else {\n\t\t\t\tin, out := &val, &outVal\n\t\t\t\t*out = make(Conditions, len(*in))\n\t\t\t\tfor i := range *in {\n\t\t\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t\t\t}\n\t\t\t}\n\t\t\t(*out)[key] = outVal\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConditionMap.\nfunc (in ConditionMap) DeepCopy() ConditionMap {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(ConditionMap)\n\tin.DeepCopyInto(out)\n\treturn *out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in Conditions) DeepCopyInto(out *Conditions) {\n\t{\n\t\tin := &in\n\t\t*out = make(Conditions, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Conditions.\nfunc (in Conditions) DeepCopy() Conditions {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(Conditions)\n\tin.DeepCopyInto(out)\n\treturn *out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *EnforcingCRD) DeepCopyInto(out *EnforcingCRD) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnforcingCRD.\nfunc (in *EnforcingCRD) DeepCopy() *EnforcingCRD {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(EnforcingCRD)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *EnforcingCRD) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *EnforcingCRDList) DeepCopyInto(out *EnforcingCRDList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]EnforcingCRD, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnforcingCRDList.\nfunc (in *EnforcingCRDList) DeepCopy() *EnforcingCRDList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(EnforcingCRDList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *EnforcingCRDList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *EnforcingCRDSpec) DeepCopyInto(out *EnforcingCRDSpec) {\n\t*out = *in\n\tif in.Resources != nil {\n\t\tin, out := &in.Resources, &out.Resources\n\t\t*out = make([]LockedResource, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnforcingCRDSpec.\nfunc (in *EnforcingCRDSpec) DeepCopy() *EnforcingCRDSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(EnforcingCRDSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *EnforcingCRDStatus) DeepCopyInto(out *EnforcingCRDStatus) {\n\t*out = *in\n\tin.EnforcingReconcileStatus.DeepCopyInto(&out.EnforcingReconcileStatus)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnforcingCRDStatus.\nfunc (in *EnforcingCRDStatus) DeepCopy() *EnforcingCRDStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(EnforcingCRDStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *EnforcingPatch) DeepCopyInto(out *EnforcingPatch) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnforcingPatch.\nfunc (in *EnforcingPatch) DeepCopy() *EnforcingPatch {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(EnforcingPatch)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *EnforcingPatch) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *EnforcingPatchList) DeepCopyInto(out *EnforcingPatchList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]EnforcingPatch, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnforcingPatchList.\nfunc (in *EnforcingPatchList) DeepCopy() *EnforcingPatchList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(EnforcingPatchList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *EnforcingPatchList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *EnforcingPatchSpec) DeepCopyInto(out *EnforcingPatchSpec) {\n\t*out = *in\n\tif in.Patches != nil {\n\t\tin, out := &in.Patches, &out.Patches\n\t\t*out = make(map[string]PatchSpec, len(*in))\n\t\tfor key, val := range *in {\n\t\t\t(*out)[key] = *val.DeepCopy()\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnforcingPatchSpec.\nfunc (in *EnforcingPatchSpec) DeepCopy() *EnforcingPatchSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(EnforcingPatchSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *EnforcingPatchStatus) DeepCopyInto(out *EnforcingPatchStatus) {\n\t*out = *in\n\tin.EnforcingReconcileStatus.DeepCopyInto(&out.EnforcingReconcileStatus)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnforcingPatchStatus.\nfunc (in *EnforcingPatchStatus) DeepCopy() *EnforcingPatchStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(EnforcingPatchStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *EnforcingReconcileStatus) DeepCopyInto(out *EnforcingReconcileStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]v1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\tif in.LockedResourceStatuses != nil {\n\t\tin, out := &in.LockedResourceStatuses, &out.LockedResourceStatuses\n\t\t*out = make(map[string]Conditions, len(*in))\n\t\tfor key, val := range *in {\n\t\t\tvar outVal []v1.Condition\n\t\t\tif val == nil {\n\t\t\t\t(*out)[key] = nil\n\t\t\t} else {\n\t\t\t\tin, out := &val, &outVal\n\t\t\t\t*out = make(Conditions, len(*in))\n\t\t\t\tfor i := range *in {\n\t\t\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t\t\t}\n\t\t\t}\n\t\t\t(*out)[key] = outVal\n\t\t}\n\t}\n\tif in.LockedPatchStatuses != nil {\n\t\tin, out := &in.LockedPatchStatuses, &out.LockedPatchStatuses\n\t\t*out = make(map[string]ConditionMap, len(*in))\n\t\tfor key, val := range *in {\n\t\t\tvar outVal map[string]Conditions\n\t\t\tif val == nil {\n\t\t\t\t(*out)[key] = nil\n\t\t\t} else {\n\t\t\t\tin, out := &val, &outVal\n\t\t\t\t*out = make(ConditionMap, len(*in))\n\t\t\t\tfor key, val := range *in {\n\t\t\t\t\tvar outVal []v1.Condition\n\t\t\t\t\tif val == nil {\n\t\t\t\t\t\t(*out)[key] = nil\n\t\t\t\t\t} else {\n\t\t\t\t\t\tin, out := &val, &outVal\n\t\t\t\t\t\t*out = make(Conditions, len(*in))\n\t\t\t\t\t\tfor i := range *in {\n\t\t\t\t\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t(*out)[key] = outVal\n\t\t\t\t}\n\t\t\t}\n\t\t\t(*out)[key] = outVal\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnforcingReconcileStatus.\nfunc (in *EnforcingReconcileStatus) DeepCopy() *EnforcingReconcileStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(EnforcingReconcileStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *LockedResource) DeepCopyInto(out *LockedResource) {\n\t*out = *in\n\tin.Object.DeepCopyInto(&out.Object)\n\tif in.ExcludedPaths != nil {\n\t\tin, out := &in.ExcludedPaths, &out.ExcludedPaths\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LockedResource.\nfunc (in *LockedResource) DeepCopy() *LockedResource {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(LockedResource)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *LockedResourceTemplate) DeepCopyInto(out *LockedResourceTemplate) {\n\t*out = *in\n\tif in.ExcludedPaths != nil {\n\t\tin, out := &in.ExcludedPaths, &out.ExcludedPaths\n\t\t*out = make([]string, len(*in))\n\t\tcopy(*out, *in)\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LockedResourceTemplate.\nfunc (in *LockedResourceTemplate) DeepCopy() *LockedResourceTemplate {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(LockedResourceTemplate)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *MyCRD) DeepCopyInto(out *MyCRD) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tout.Spec = in.Spec\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MyCRD.\nfunc (in *MyCRD) DeepCopy() *MyCRD {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(MyCRD)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *MyCRD) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *MyCRDList) DeepCopyInto(out *MyCRDList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]MyCRD, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MyCRDList.\nfunc (in *MyCRDList) DeepCopy() *MyCRDList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(MyCRDList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *MyCRDList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *MyCRDSpec) DeepCopyInto(out *MyCRDSpec) {\n\t*out = *in\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MyCRDSpec.\nfunc (in *MyCRDSpec) DeepCopy() *MyCRDSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(MyCRDSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *MyCRDStatus) DeepCopyInto(out *MyCRDStatus) {\n\t*out = *in\n\tif in.Conditions != nil {\n\t\tin, out := &in.Conditions, &out.Conditions\n\t\t*out = make([]v1.Condition, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MyCRDStatus.\nfunc (in *MyCRDStatus) DeepCopy() *MyCRDStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(MyCRDStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *PatchSpec) DeepCopyInto(out *PatchSpec) {\n\t*out = *in\n\tif in.SourceObjectRefs != nil {\n\t\tin, out := &in.SourceObjectRefs, &out.SourceObjectRefs\n\t\t*out = make([]SourceObjectReference, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n\tin.TargetObjectRef.DeepCopyInto(&out.TargetObjectRef)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PatchSpec.\nfunc (in *PatchSpec) DeepCopy() *PatchSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(PatchSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *SourceObjectReference) DeepCopyInto(out *SourceObjectReference) {\n\t*out = *in\n\tif in.apiResource != nil {\n\t\tin, out := &in.apiResource, &out.apiResource\n\t\t*out = new(v1.APIResource)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SourceObjectReference.\nfunc (in *SourceObjectReference) DeepCopy() *SourceObjectReference {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(SourceObjectReference)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *TargetObjectReference) DeepCopyInto(out *TargetObjectReference) {\n\t*out = *in\n\tif in.LabelSelector != nil {\n\t\tin, out := &in.LabelSelector, &out.LabelSelector\n\t\t*out = new(v1.LabelSelector)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.AnnotationSelector != nil {\n\t\tin, out := &in.AnnotationSelector, &out.AnnotationSelector\n\t\t*out = new(v1.LabelSelector)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n\tif in.apiResource != nil {\n\t\tin, out := &in.apiResource, &out.apiResource\n\t\t*out = new(v1.APIResource)\n\t\t(*in).DeepCopyInto(*out)\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetObjectReference.\nfunc (in *TargetObjectReference) DeepCopy() *TargetObjectReference {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(TargetObjectReference)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *TemplatedEnforcingCRD) DeepCopyInto(out *TemplatedEnforcingCRD) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ObjectMeta.DeepCopyInto(&out.ObjectMeta)\n\tin.Spec.DeepCopyInto(&out.Spec)\n\tin.Status.DeepCopyInto(&out.Status)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TemplatedEnforcingCRD.\nfunc (in *TemplatedEnforcingCRD) DeepCopy() *TemplatedEnforcingCRD {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(TemplatedEnforcingCRD)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *TemplatedEnforcingCRD) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *TemplatedEnforcingCRDList) DeepCopyInto(out *TemplatedEnforcingCRDList) {\n\t*out = *in\n\tout.TypeMeta = in.TypeMeta\n\tin.ListMeta.DeepCopyInto(&out.ListMeta)\n\tif in.Items != nil {\n\t\tin, out := &in.Items, &out.Items\n\t\t*out = make([]TemplatedEnforcingCRD, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TemplatedEnforcingCRDList.\nfunc (in *TemplatedEnforcingCRDList) DeepCopy() *TemplatedEnforcingCRDList {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(TemplatedEnforcingCRDList)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.\nfunc (in *TemplatedEnforcingCRDList) DeepCopyObject() runtime.Object {\n\tif c := in.DeepCopy(); c != nil {\n\t\treturn c\n\t}\n\treturn nil\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *TemplatedEnforcingCRDSpec) DeepCopyInto(out *TemplatedEnforcingCRDSpec) {\n\t*out = *in\n\tif in.Templates != nil {\n\t\tin, out := &in.Templates, &out.Templates\n\t\t*out = make([]LockedResourceTemplate, len(*in))\n\t\tfor i := range *in {\n\t\t\t(*in)[i].DeepCopyInto(&(*out)[i])\n\t\t}\n\t}\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TemplatedEnforcingCRDSpec.\nfunc (in *TemplatedEnforcingCRDSpec) DeepCopy() *TemplatedEnforcingCRDSpec {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(TemplatedEnforcingCRDSpec)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n\n// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.\nfunc (in *TemplatedEnforcingCRDStatus) DeepCopyInto(out *TemplatedEnforcingCRDStatus) {\n\t*out = *in\n\tin.EnforcingReconcileStatus.DeepCopyInto(&out.EnforcingReconcileStatus)\n}\n\n// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TemplatedEnforcingCRDStatus.\nfunc (in *TemplatedEnforcingCRDStatus) DeepCopy() *TemplatedEnforcingCRDStatus {\n\tif in == nil {\n\t\treturn nil\n\t}\n\tout := new(TemplatedEnforcingCRDStatus)\n\tin.DeepCopyInto(out)\n\treturn out\n}\n"
  },
  {
    "path": "ci.Dockerfile",
    "content": "FROM registry.access.redhat.com/ubi8/ubi-minimal\nWORKDIR /\nCOPY bin/manager .\nUSER 65532:65532\n\nENTRYPOINT [\"/manager\"]"
  },
  {
    "path": "config/certmanager/certificate.yaml",
    "content": "# The following manifests contain a self-signed issuer CR and a certificate CR.\n# More document can be found at https://docs.cert-manager.io\n# WARNING: Targets CertManager 0.11 check https://docs.cert-manager.io/en/latest/tasks/upgrading/index.html for \n# breaking changes\napiVersion: cert-manager.io/v1alpha2\nkind: Issuer\nmetadata:\n  name: selfsigned-issuer\n  namespace: system\nspec:\n  selfSigned: {}\n---\napiVersion: cert-manager.io/v1alpha2\nkind: Certificate\nmetadata:\n  name: serving-cert  # this name should match the one appeared in kustomizeconfig.yaml\n  namespace: system\nspec:\n  # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize\n  dnsNames:\n  - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc\n  - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local\n  issuerRef:\n    kind: Issuer\n    name: selfsigned-issuer\n  secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize\n"
  },
  {
    "path": "config/certmanager/kustomization.yaml",
    "content": "resources:\n- certificate.yaml\n\nconfigurations:\n- kustomizeconfig.yaml\n"
  },
  {
    "path": "config/certmanager/kustomizeconfig.yaml",
    "content": "# This configuration is for teaching kustomize how to update name ref and var substitution \nnameReference:\n- kind: Issuer\n  group: cert-manager.io\n  fieldSpecs:\n  - kind: Certificate\n    group: cert-manager.io\n    path: spec/issuerRef/name\n\nvarReference:\n- kind: Certificate\n  group: cert-manager.io\n  path: spec/commonName\n- kind: Certificate\n  group: cert-manager.io\n  path: spec/dnsNames\n"
  },
  {
    "path": "config/crd/bases/operator-utils.example.io_enforcingcrds.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.9.0\n  creationTimestamp: null\n  name: enforcingcrds.operator-utils.example.io\nspec:\n  group: operator-utils.example.io\n  names:\n    kind: EnforcingCRD\n    listKind: EnforcingCRDList\n    plural: enforcingcrds\n    singular: enforcingcrd\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: EnforcingCRD is the Schema for the enforcingcrds API\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: EnforcingCRDSpec defines the desired state of EnforcingCRD\n            properties:\n              resources:\n                description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n                  Important: Run \"operator-sdk generate k8s\" to regenerate code after\n                  modifying this file Add custom validation using kubebuilder tags:\n                  https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html\n                  Resources is a list of resource manifests that should be locked\n                  into the specified configuration'\n                items:\n                  description: LockedResource represents a resource to be enforced\n                    in a LockedResourceController and can be used in a API specification\n                  properties:\n                    excludedPaths:\n                      description: ExludedPaths are a set of json paths that need\n                        not be considered by the LockedResourceReconciler\n                      items:\n                        type: string\n                      type: array\n                      x-kubernetes-list-type: set\n                    object:\n                      description: Object is a yaml representation of an API resource\n                      type: object\n                  required:\n                  - object\n                  type: object\n                type: array\n                x-kubernetes-list-type: atomic\n            type: object\n          status:\n            description: EnforcingCRDStatus defines the observed state of EnforcingCRD\n            properties:\n              conditions:\n                description: ReconcileStatus this is the general status of the main\n                  reconciler\n                items:\n                  description: \"Condition contains details for one aspect of the current\n                    state of this API Resource. --- This struct is intended for direct\n                    use as an array at the field path .status.conditions.  For example,\n                    \\n type FooStatus struct{ // Represents the observations of a\n                    foo's current state. // Known .status.conditions.type are: \\\"Available\\\",\n                    \\\"Progressing\\\", and \\\"Degraded\\\" // +patchMergeKey=type // +patchStrategy=merge\n                    // +listType=map // +listMapKey=type Conditions []metav1.Condition\n                    `json:\\\"conditions,omitempty\\\" patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\"\n                    protobuf:\\\"bytes,1,rep,name=conditions\\\"` \\n // other fields }\"\n                  properties:\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the condition\n                        transitioned from one status to another. This should be when\n                        the underlying condition changed.  If that is not known, then\n                        using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: message is a human readable message indicating\n                        details about the transition. This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: observedGeneration represents the .metadata.generation\n                        that the condition was set based upon. For instance, if .metadata.generation\n                        is currently 12, but the .status.conditions[x].observedGeneration\n                        is 9, the condition is out of date with respect to the current\n                        state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: reason contains a programmatic identifier indicating\n                        the reason for the condition's last transition. Producers\n                        of specific condition types may define expected values and\n                        meanings for this field, and whether the values are considered\n                        a guaranteed API. The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                        --- Many .condition.type values are consistent across resources\n                        like Available, but because arbitrary conditions can be useful\n                        (see .node.status.conditions), the ability to deconflict is\n                        important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n              lockedPatchStatuses:\n                additionalProperties:\n                  additionalProperties:\n                    items:\n                      description: \"Condition contains details for one aspect of the\n                        current state of this API Resource. --- This struct is intended\n                        for direct use as an array at the field path .status.conditions.\n                        \\ For example, \\n type FooStatus struct{ // Represents the\n                        observations of a foo's current state. // Known .status.conditions.type\n                        are: \\\"Available\\\", \\\"Progressing\\\", and \\\"Degraded\\\" // +patchMergeKey=type\n                        // +patchStrategy=merge // +listType=map // +listMapKey=type\n                        Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                        patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                        \\n // other fields }\"\n                      properties:\n                        lastTransitionTime:\n                          description: lastTransitionTime is the last time the condition\n                            transitioned from one status to another. This should be\n                            when the underlying condition changed.  If that is not\n                            known, then using the time when the API field changed\n                            is acceptable.\n                          format: date-time\n                          type: string\n                        message:\n                          description: message is a human readable message indicating\n                            details about the transition. This may be an empty string.\n                          maxLength: 32768\n                          type: string\n                        observedGeneration:\n                          description: observedGeneration represents the .metadata.generation\n                            that the condition was set based upon. For instance, if\n                            .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                            is 9, the condition is out of date with respect to the\n                            current state of the instance.\n                          format: int64\n                          minimum: 0\n                          type: integer\n                        reason:\n                          description: reason contains a programmatic identifier indicating\n                            the reason for the condition's last transition. Producers\n                            of specific condition types may define expected values\n                            and meanings for this field, and whether the values are\n                            considered a guaranteed API. The value should be a CamelCase\n                            string. This field may not be empty.\n                          maxLength: 1024\n                          minLength: 1\n                          pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                          type: string\n                        status:\n                          description: status of the condition, one of True, False,\n                            Unknown.\n                          enum:\n                          - \"True\"\n                          - \"False\"\n                          - Unknown\n                          type: string\n                        type:\n                          description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                            --- Many .condition.type values are consistent across\n                            resources like Available, but because arbitrary conditions\n                            can be useful (see .node.status.conditions), the ability\n                            to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                          maxLength: 316\n                          pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                          type: string\n                      required:\n                      - lastTransitionTime\n                      - message\n                      - reason\n                      - status\n                      - type\n                      type: object\n                    type: array\n                  type: object\n                description: LockedResourceStatuses contains the reconcile status\n                  for each of the managed resources\n                type: object\n              lockedResourceStatuses:\n                additionalProperties:\n                  items:\n                    description: \"Condition contains details for one aspect of the\n                      current state of this API Resource. --- This struct is intended\n                      for direct use as an array at the field path .status.conditions.\n                      \\ For example, \\n type FooStatus struct{ // Represents the observations\n                      of a foo's current state. // Known .status.conditions.type are:\n                      \\\"Available\\\", \\\"Progressing\\\", and \\\"Degraded\\\" // +patchMergeKey=type\n                      // +patchStrategy=merge // +listType=map // +listMapKey=type\n                      Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                      patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                      \\n // other fields }\"\n                    properties:\n                      lastTransitionTime:\n                        description: lastTransitionTime is the last time the condition\n                          transitioned from one status to another. This should be\n                          when the underlying condition changed.  If that is not known,\n                          then using the time when the API field changed is acceptable.\n                        format: date-time\n                        type: string\n                      message:\n                        description: message is a human readable message indicating\n                          details about the transition. This may be an empty string.\n                        maxLength: 32768\n                        type: string\n                      observedGeneration:\n                        description: observedGeneration represents the .metadata.generation\n                          that the condition was set based upon. For instance, if\n                          .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                          is 9, the condition is out of date with respect to the current\n                          state of the instance.\n                        format: int64\n                        minimum: 0\n                        type: integer\n                      reason:\n                        description: reason contains a programmatic identifier indicating\n                          the reason for the condition's last transition. Producers\n                          of specific condition types may define expected values and\n                          meanings for this field, and whether the values are considered\n                          a guaranteed API. The value should be a CamelCase string.\n                          This field may not be empty.\n                        maxLength: 1024\n                        minLength: 1\n                        pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                        type: string\n                      status:\n                        description: status of the condition, one of True, False,\n                          Unknown.\n                        enum:\n                        - \"True\"\n                        - \"False\"\n                        - Unknown\n                        type: string\n                      type:\n                        description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                          --- Many .condition.type values are consistent across resources\n                          like Available, but because arbitrary conditions can be\n                          useful (see .node.status.conditions), the ability to deconflict\n                          is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                        maxLength: 316\n                        pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                        type: string\n                    required:\n                    - lastTransitionTime\n                    - message\n                    - reason\n                    - status\n                    - type\n                    type: object\n                  type: array\n                description: LockedResourceStatuses contains the reconcile status\n                  for each of the managed resources\n                type: object\n            type: object\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n"
  },
  {
    "path": "config/crd/bases/operator-utils.example.io_enforcingpatches.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.9.0\n  creationTimestamp: null\n  name: enforcingpatches.operator-utils.example.io\nspec:\n  group: operator-utils.example.io\n  names:\n    kind: EnforcingPatch\n    listKind: EnforcingPatchList\n    plural: enforcingpatches\n    singular: enforcingpatch\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: EnforcingPatch is the Schema for the enforcingpatches API\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: EnforcingPatchSpec defines the desired state of EnforcingPatch\n            properties:\n              patches:\n                additionalProperties:\n                  description: Patch describes a patch to be enforced at runtime\n                  properties:\n                    patchTemplate:\n                      description: PatchTemplate is a go template that will be resolved\n                        using the SourceObjectRefs as parameters. The result must\n                        be a valid patch based on the pacth type and the target object.\n                      type: string\n                    patchType:\n                      description: PatchType is the type of patch to be applied, one\n                        of \"application/json-patch+json\"'\"application/merge-patch+json\",\"application/strategic-merge-patch+json\",\"application/apply-patch+yaml\"\n                        default:=\"application/strategic-merge-patch+json\"\n                      enum:\n                      - application/json-patch+json\n                      - application/merge-patch+json\n                      - application/strategic-merge-patch+json\n                      - application/apply-patch+yaml\n                      type: string\n                    sourceObjectRefs:\n                      description: 'SourceObjectRefs is an arrays of refereces to\n                        source objects that will be used as input for the template\n                        processing. These refernces must resolve to single instance.\n                        The resolution rule is as follows (+ present, - absent): the\n                        King and APIVersion field are mandatory -Namespace +Name:\n                        resolves to cluster-level object <Name>. If Kind is namespaced,\n                        this results in an error. -Namespace -Name: results in an\n                        error Name manespaces Namespace are evaluated as golang templates\n                        with the input of the template being the target object. When\n                        selecting multiple target, this allows for having specific\n                        source objects for each target. ResourceVersion and UID are\n                        always ignored If FieldPath is specified, the restuned object\n                        is calculated from the path, so for example if FieldPath=.spec,\n                        the only the spec portion of the object is returned. The target\n                        object is always added as element zero of the array of the\n                        SourceObjectRefs'\n                      items:\n                        properties:\n                          apiVersion:\n                            description: API version of the referent.\n                            type: string\n                          fieldPath:\n                            description: 'If referring to a piece of an object instead\n                              of an entire object, this string should contain a valid\n                              JSON/Go field access statement, such as desiredState.manifest.containers[2].\n                              For example, if the object reference is to a container\n                              within a pod, this would take on a value like: \"spec.containers{name}\"\n                              (where \"name\" refers to the name of the container that\n                              triggered the event) or if no container name is specified\n                              \"spec.containers[2]\" (container with index 2 in this\n                              pod). This syntax is chosen only to have some well-defined\n                              way of referencing a part of an object.'\n                            type: string\n                          kind:\n                            description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n                            type: string\n                          name:\n                            description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'\n                            type: string\n                          namespace:\n                            description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'\n                            type: string\n                        type: object\n                      type: array\n                      x-kubernetes-list-type: atomic\n                    targetObjectRef:\n                      description: 'TargetObjectRef is a reference to the object to\n                        which the pacth should be applied. the King and APIVersion\n                        field are mandatory the Name and Namespace field have the\n                        following meaning (+ present, - absent) -Namespace +Name:\n                        apply the patch to the cluster-level object <Name>. If Kind\n                        is namespaced, this results in an error. -Namespace -Name:\n                        if the kind is namespaced apply the patch to all of the objects\n                        in all of the namespaces. If the kind is not namespaced, apply\n                        the patch to all of the cluster level objects. The lable selector\n                        can be used to further filter the selected objects.'\n                      properties:\n                        annotationSelector:\n                          description: AnnotationSelector selects objects by label\n                          properties:\n                            matchExpressions:\n                              description: matchExpressions is a list of label selector\n                                requirements. The requirements are ANDed.\n                              items:\n                                description: A label selector requirement is a selector\n                                  that contains values, a key, and an operator that\n                                  relates the key and values.\n                                properties:\n                                  key:\n                                    description: key is the label key that the selector\n                                      applies to.\n                                    type: string\n                                  operator:\n                                    description: operator represents a key's relationship\n                                      to a set of values. Valid operators are In,\n                                      NotIn, Exists and DoesNotExist.\n                                    type: string\n                                  values:\n                                    description: values is an array of string values.\n                                      If the operator is In or NotIn, the values array\n                                      must be non-empty. If the operator is Exists\n                                      or DoesNotExist, the values array must be empty.\n                                      This array is replaced during a strategic merge\n                                      patch.\n                                    items:\n                                      type: string\n                                    type: array\n                                required:\n                                - key\n                                - operator\n                                type: object\n                              type: array\n                            matchLabels:\n                              additionalProperties:\n                                type: string\n                              description: matchLabels is a map of {key,value} pairs.\n                                A single {key,value} in the matchLabels map is equivalent\n                                to an element of matchExpressions, whose key field\n                                is \"key\", the operator is \"In\", and the values array\n                                contains only \"value\". The requirements are ANDed.\n                              type: object\n                          type: object\n                        apiVersion:\n                          description: API version of the referent.\n                          type: string\n                        kind:\n                          description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n                          type: string\n                        labelSelector:\n                          description: LabelSelector selects objects by label\n                          properties:\n                            matchExpressions:\n                              description: matchExpressions is a list of label selector\n                                requirements. The requirements are ANDed.\n                              items:\n                                description: A label selector requirement is a selector\n                                  that contains values, a key, and an operator that\n                                  relates the key and values.\n                                properties:\n                                  key:\n                                    description: key is the label key that the selector\n                                      applies to.\n                                    type: string\n                                  operator:\n                                    description: operator represents a key's relationship\n                                      to a set of values. Valid operators are In,\n                                      NotIn, Exists and DoesNotExist.\n                                    type: string\n                                  values:\n                                    description: values is an array of string values.\n                                      If the operator is In or NotIn, the values array\n                                      must be non-empty. If the operator is Exists\n                                      or DoesNotExist, the values array must be empty.\n                                      This array is replaced during a strategic merge\n                                      patch.\n                                    items:\n                                      type: string\n                                    type: array\n                                required:\n                                - key\n                                - operator\n                                type: object\n                              type: array\n                            matchLabels:\n                              additionalProperties:\n                                type: string\n                              description: matchLabels is a map of {key,value} pairs.\n                                A single {key,value} in the matchLabels map is equivalent\n                                to an element of matchExpressions, whose key field\n                                is \"key\", the operator is \"In\", and the values array\n                                contains only \"value\". The requirements are ANDed.\n                              type: object\n                          type: object\n                        name:\n                          description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names'\n                          type: string\n                        namespace:\n                          description: 'Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/'\n                          type: string\n                      type: object\n                  type: object\n                description: Patches is a list of pacthes that should be encforced\n                  at runtime.\n                type: object\n            type: object\n          status:\n            description: EnforcingPatchStatus defines the observed state of EnforcingPatch\n            properties:\n              conditions:\n                description: ReconcileStatus this is the general status of the main\n                  reconciler\n                items:\n                  description: \"Condition contains details for one aspect of the current\n                    state of this API Resource. --- This struct is intended for direct\n                    use as an array at the field path .status.conditions.  For example,\n                    \\n type FooStatus struct{ // Represents the observations of a\n                    foo's current state. // Known .status.conditions.type are: \\\"Available\\\",\n                    \\\"Progressing\\\", and \\\"Degraded\\\" // +patchMergeKey=type // +patchStrategy=merge\n                    // +listType=map // +listMapKey=type Conditions []metav1.Condition\n                    `json:\\\"conditions,omitempty\\\" patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\"\n                    protobuf:\\\"bytes,1,rep,name=conditions\\\"` \\n // other fields }\"\n                  properties:\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the condition\n                        transitioned from one status to another. This should be when\n                        the underlying condition changed.  If that is not known, then\n                        using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: message is a human readable message indicating\n                        details about the transition. This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: observedGeneration represents the .metadata.generation\n                        that the condition was set based upon. For instance, if .metadata.generation\n                        is currently 12, but the .status.conditions[x].observedGeneration\n                        is 9, the condition is out of date with respect to the current\n                        state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: reason contains a programmatic identifier indicating\n                        the reason for the condition's last transition. Producers\n                        of specific condition types may define expected values and\n                        meanings for this field, and whether the values are considered\n                        a guaranteed API. The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                        --- Many .condition.type values are consistent across resources\n                        like Available, but because arbitrary conditions can be useful\n                        (see .node.status.conditions), the ability to deconflict is\n                        important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n              lockedPatchStatuses:\n                additionalProperties:\n                  additionalProperties:\n                    items:\n                      description: \"Condition contains details for one aspect of the\n                        current state of this API Resource. --- This struct is intended\n                        for direct use as an array at the field path .status.conditions.\n                        \\ For example, \\n type FooStatus struct{ // Represents the\n                        observations of a foo's current state. // Known .status.conditions.type\n                        are: \\\"Available\\\", \\\"Progressing\\\", and \\\"Degraded\\\" // +patchMergeKey=type\n                        // +patchStrategy=merge // +listType=map // +listMapKey=type\n                        Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                        patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                        \\n // other fields }\"\n                      properties:\n                        lastTransitionTime:\n                          description: lastTransitionTime is the last time the condition\n                            transitioned from one status to another. This should be\n                            when the underlying condition changed.  If that is not\n                            known, then using the time when the API field changed\n                            is acceptable.\n                          format: date-time\n                          type: string\n                        message:\n                          description: message is a human readable message indicating\n                            details about the transition. This may be an empty string.\n                          maxLength: 32768\n                          type: string\n                        observedGeneration:\n                          description: observedGeneration represents the .metadata.generation\n                            that the condition was set based upon. For instance, if\n                            .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                            is 9, the condition is out of date with respect to the\n                            current state of the instance.\n                          format: int64\n                          minimum: 0\n                          type: integer\n                        reason:\n                          description: reason contains a programmatic identifier indicating\n                            the reason for the condition's last transition. Producers\n                            of specific condition types may define expected values\n                            and meanings for this field, and whether the values are\n                            considered a guaranteed API. The value should be a CamelCase\n                            string. This field may not be empty.\n                          maxLength: 1024\n                          minLength: 1\n                          pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                          type: string\n                        status:\n                          description: status of the condition, one of True, False,\n                            Unknown.\n                          enum:\n                          - \"True\"\n                          - \"False\"\n                          - Unknown\n                          type: string\n                        type:\n                          description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                            --- Many .condition.type values are consistent across\n                            resources like Available, but because arbitrary conditions\n                            can be useful (see .node.status.conditions), the ability\n                            to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                          maxLength: 316\n                          pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                          type: string\n                      required:\n                      - lastTransitionTime\n                      - message\n                      - reason\n                      - status\n                      - type\n                      type: object\n                    type: array\n                  type: object\n                description: LockedResourceStatuses contains the reconcile status\n                  for each of the managed resources\n                type: object\n              lockedResourceStatuses:\n                additionalProperties:\n                  items:\n                    description: \"Condition contains details for one aspect of the\n                      current state of this API Resource. --- This struct is intended\n                      for direct use as an array at the field path .status.conditions.\n                      \\ For example, \\n type FooStatus struct{ // Represents the observations\n                      of a foo's current state. // Known .status.conditions.type are:\n                      \\\"Available\\\", \\\"Progressing\\\", and \\\"Degraded\\\" // +patchMergeKey=type\n                      // +patchStrategy=merge // +listType=map // +listMapKey=type\n                      Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                      patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                      \\n // other fields }\"\n                    properties:\n                      lastTransitionTime:\n                        description: lastTransitionTime is the last time the condition\n                          transitioned from one status to another. This should be\n                          when the underlying condition changed.  If that is not known,\n                          then using the time when the API field changed is acceptable.\n                        format: date-time\n                        type: string\n                      message:\n                        description: message is a human readable message indicating\n                          details about the transition. This may be an empty string.\n                        maxLength: 32768\n                        type: string\n                      observedGeneration:\n                        description: observedGeneration represents the .metadata.generation\n                          that the condition was set based upon. For instance, if\n                          .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                          is 9, the condition is out of date with respect to the current\n                          state of the instance.\n                        format: int64\n                        minimum: 0\n                        type: integer\n                      reason:\n                        description: reason contains a programmatic identifier indicating\n                          the reason for the condition's last transition. Producers\n                          of specific condition types may define expected values and\n                          meanings for this field, and whether the values are considered\n                          a guaranteed API. The value should be a CamelCase string.\n                          This field may not be empty.\n                        maxLength: 1024\n                        minLength: 1\n                        pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                        type: string\n                      status:\n                        description: status of the condition, one of True, False,\n                          Unknown.\n                        enum:\n                        - \"True\"\n                        - \"False\"\n                        - Unknown\n                        type: string\n                      type:\n                        description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                          --- Many .condition.type values are consistent across resources\n                          like Available, but because arbitrary conditions can be\n                          useful (see .node.status.conditions), the ability to deconflict\n                          is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                        maxLength: 316\n                        pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                        type: string\n                    required:\n                    - lastTransitionTime\n                    - message\n                    - reason\n                    - status\n                    - type\n                    type: object\n                  type: array\n                description: LockedResourceStatuses contains the reconcile status\n                  for each of the managed resources\n                type: object\n            type: object\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n"
  },
  {
    "path": "config/crd/bases/operator-utils.example.io_mycrds.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.9.0\n  creationTimestamp: null\n  name: mycrds.operator-utils.example.io\nspec:\n  group: operator-utils.example.io\n  names:\n    kind: MyCRD\n    listKind: MyCRDList\n    plural: mycrds\n    singular: mycrd\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: MyCRD is the Schema for the mycrds API\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: MyCRDSpec defines the desired state of MyCRD\n            properties:\n              error:\n                type: boolean\n              initialized:\n                description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n                  Important: Run \"operator-sdk generate k8s\" to regenerate code after\n                  modifying this file Add custom validation using kubebuilder tags:\n                  https://book.kubebuilder.io/beyond_basics/generating_crd.html'\n                type: boolean\n              valid:\n                type: boolean\n            required:\n            - error\n            - initialized\n            - valid\n            type: object\n          status:\n            description: MyCRDStatus defines the observed state of MyCRD\n            properties:\n              conditions:\n                items:\n                  description: \"Condition contains details for one aspect of the current\n                    state of this API Resource. --- This struct is intended for direct\n                    use as an array at the field path .status.conditions.  For example,\n                    \\n type FooStatus struct{ // Represents the observations of a\n                    foo's current state. // Known .status.conditions.type are: \\\"Available\\\",\n                    \\\"Progressing\\\", and \\\"Degraded\\\" // +patchMergeKey=type // +patchStrategy=merge\n                    // +listType=map // +listMapKey=type Conditions []metav1.Condition\n                    `json:\\\"conditions,omitempty\\\" patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\"\n                    protobuf:\\\"bytes,1,rep,name=conditions\\\"` \\n // other fields }\"\n                  properties:\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the condition\n                        transitioned from one status to another. This should be when\n                        the underlying condition changed.  If that is not known, then\n                        using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: message is a human readable message indicating\n                        details about the transition. This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: observedGeneration represents the .metadata.generation\n                        that the condition was set based upon. For instance, if .metadata.generation\n                        is currently 12, but the .status.conditions[x].observedGeneration\n                        is 9, the condition is out of date with respect to the current\n                        state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: reason contains a programmatic identifier indicating\n                        the reason for the condition's last transition. Producers\n                        of specific condition types may define expected values and\n                        meanings for this field, and whether the values are considered\n                        a guaranteed API. The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                        --- Many .condition.type values are consistent across resources\n                        like Available, but because arbitrary conditions can be useful\n                        (see .node.status.conditions), the ability to deconflict is\n                        important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n            type: object\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n"
  },
  {
    "path": "config/crd/bases/operator-utils.example.io_templatedenforcingcrds.yaml",
    "content": "---\napiVersion: apiextensions.k8s.io/v1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    controller-gen.kubebuilder.io/version: v0.9.0\n  creationTimestamp: null\n  name: templatedenforcingcrds.operator-utils.example.io\nspec:\n  group: operator-utils.example.io\n  names:\n    kind: TemplatedEnforcingCRD\n    listKind: TemplatedEnforcingCRDList\n    plural: templatedenforcingcrds\n    singular: templatedenforcingcrd\n  scope: Namespaced\n  versions:\n  - name: v1alpha1\n    schema:\n      openAPIV3Schema:\n        description: TemplatedEnforcingCRD is the Schema for the templatedenforcingcrds\n          API\n        properties:\n          apiVersion:\n            description: 'APIVersion defines the versioned schema of this representation\n              of an object. Servers should convert recognized schemas to the latest\n              internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'\n            type: string\n          kind:\n            description: 'Kind is a string value representing the REST resource this\n              object represents. Servers may infer this from the endpoint the client\n              submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'\n            type: string\n          metadata:\n            type: object\n          spec:\n            description: TemplatedEnforcingCRDSpec defines the desired state of TemplatedEnforcingCRD\n            properties:\n              templates:\n                description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster\n                  Important: Run \"operator-sdk generate k8s\" to regenerate code after\n                  modifying this file Add custom validation using kubebuilder tags:\n                  https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html'\n                items:\n                  description: LockedResourceTemplate represents a resource template\n                    in go language to be enforced in a LockedResourceController and\n                    can be used in a API specification\n                  properties:\n                    excludedPaths:\n                      description: ExludedPaths are a set of json paths that need\n                        not be considered by the LockedResourceReconciler\n                      items:\n                        type: string\n                      type: array\n                      x-kubernetes-list-type: set\n                    objectTemplate:\n                      description: ObjectTemplate is a goland template. Whne processed,\n                        it must resolve to a yaml representation of an API resource\n                      type: string\n                  required:\n                  - objectTemplate\n                  type: object\n                type: array\n                x-kubernetes-list-type: atomic\n            type: object\n          status:\n            description: TemplatedEnforcingCRDStatus defines the observed state of\n              TemplatedEnforcingCRD\n            properties:\n              conditions:\n                description: ReconcileStatus this is the general status of the main\n                  reconciler\n                items:\n                  description: \"Condition contains details for one aspect of the current\n                    state of this API Resource. --- This struct is intended for direct\n                    use as an array at the field path .status.conditions.  For example,\n                    \\n type FooStatus struct{ // Represents the observations of a\n                    foo's current state. // Known .status.conditions.type are: \\\"Available\\\",\n                    \\\"Progressing\\\", and \\\"Degraded\\\" // +patchMergeKey=type // +patchStrategy=merge\n                    // +listType=map // +listMapKey=type Conditions []metav1.Condition\n                    `json:\\\"conditions,omitempty\\\" patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\"\n                    protobuf:\\\"bytes,1,rep,name=conditions\\\"` \\n // other fields }\"\n                  properties:\n                    lastTransitionTime:\n                      description: lastTransitionTime is the last time the condition\n                        transitioned from one status to another. This should be when\n                        the underlying condition changed.  If that is not known, then\n                        using the time when the API field changed is acceptable.\n                      format: date-time\n                      type: string\n                    message:\n                      description: message is a human readable message indicating\n                        details about the transition. This may be an empty string.\n                      maxLength: 32768\n                      type: string\n                    observedGeneration:\n                      description: observedGeneration represents the .metadata.generation\n                        that the condition was set based upon. For instance, if .metadata.generation\n                        is currently 12, but the .status.conditions[x].observedGeneration\n                        is 9, the condition is out of date with respect to the current\n                        state of the instance.\n                      format: int64\n                      minimum: 0\n                      type: integer\n                    reason:\n                      description: reason contains a programmatic identifier indicating\n                        the reason for the condition's last transition. Producers\n                        of specific condition types may define expected values and\n                        meanings for this field, and whether the values are considered\n                        a guaranteed API. The value should be a CamelCase string.\n                        This field may not be empty.\n                      maxLength: 1024\n                      minLength: 1\n                      pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                      type: string\n                    status:\n                      description: status of the condition, one of True, False, Unknown.\n                      enum:\n                      - \"True\"\n                      - \"False\"\n                      - Unknown\n                      type: string\n                    type:\n                      description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                        --- Many .condition.type values are consistent across resources\n                        like Available, but because arbitrary conditions can be useful\n                        (see .node.status.conditions), the ability to deconflict is\n                        important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                      maxLength: 316\n                      pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                      type: string\n                  required:\n                  - lastTransitionTime\n                  - message\n                  - reason\n                  - status\n                  - type\n                  type: object\n                type: array\n                x-kubernetes-list-map-keys:\n                - type\n                x-kubernetes-list-type: map\n              lockedPatchStatuses:\n                additionalProperties:\n                  additionalProperties:\n                    items:\n                      description: \"Condition contains details for one aspect of the\n                        current state of this API Resource. --- This struct is intended\n                        for direct use as an array at the field path .status.conditions.\n                        \\ For example, \\n type FooStatus struct{ // Represents the\n                        observations of a foo's current state. // Known .status.conditions.type\n                        are: \\\"Available\\\", \\\"Progressing\\\", and \\\"Degraded\\\" // +patchMergeKey=type\n                        // +patchStrategy=merge // +listType=map // +listMapKey=type\n                        Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                        patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                        \\n // other fields }\"\n                      properties:\n                        lastTransitionTime:\n                          description: lastTransitionTime is the last time the condition\n                            transitioned from one status to another. This should be\n                            when the underlying condition changed.  If that is not\n                            known, then using the time when the API field changed\n                            is acceptable.\n                          format: date-time\n                          type: string\n                        message:\n                          description: message is a human readable message indicating\n                            details about the transition. This may be an empty string.\n                          maxLength: 32768\n                          type: string\n                        observedGeneration:\n                          description: observedGeneration represents the .metadata.generation\n                            that the condition was set based upon. For instance, if\n                            .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                            is 9, the condition is out of date with respect to the\n                            current state of the instance.\n                          format: int64\n                          minimum: 0\n                          type: integer\n                        reason:\n                          description: reason contains a programmatic identifier indicating\n                            the reason for the condition's last transition. Producers\n                            of specific condition types may define expected values\n                            and meanings for this field, and whether the values are\n                            considered a guaranteed API. The value should be a CamelCase\n                            string. This field may not be empty.\n                          maxLength: 1024\n                          minLength: 1\n                          pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                          type: string\n                        status:\n                          description: status of the condition, one of True, False,\n                            Unknown.\n                          enum:\n                          - \"True\"\n                          - \"False\"\n                          - Unknown\n                          type: string\n                        type:\n                          description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                            --- Many .condition.type values are consistent across\n                            resources like Available, but because arbitrary conditions\n                            can be useful (see .node.status.conditions), the ability\n                            to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                          maxLength: 316\n                          pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                          type: string\n                      required:\n                      - lastTransitionTime\n                      - message\n                      - reason\n                      - status\n                      - type\n                      type: object\n                    type: array\n                  type: object\n                description: LockedResourceStatuses contains the reconcile status\n                  for each of the managed resources\n                type: object\n              lockedResourceStatuses:\n                additionalProperties:\n                  items:\n                    description: \"Condition contains details for one aspect of the\n                      current state of this API Resource. --- This struct is intended\n                      for direct use as an array at the field path .status.conditions.\n                      \\ For example, \\n type FooStatus struct{ // Represents the observations\n                      of a foo's current state. // Known .status.conditions.type are:\n                      \\\"Available\\\", \\\"Progressing\\\", and \\\"Degraded\\\" // +patchMergeKey=type\n                      // +patchStrategy=merge // +listType=map // +listMapKey=type\n                      Conditions []metav1.Condition `json:\\\"conditions,omitempty\\\"\n                      patchStrategy:\\\"merge\\\" patchMergeKey:\\\"type\\\" protobuf:\\\"bytes,1,rep,name=conditions\\\"`\n                      \\n // other fields }\"\n                    properties:\n                      lastTransitionTime:\n                        description: lastTransitionTime is the last time the condition\n                          transitioned from one status to another. This should be\n                          when the underlying condition changed.  If that is not known,\n                          then using the time when the API field changed is acceptable.\n                        format: date-time\n                        type: string\n                      message:\n                        description: message is a human readable message indicating\n                          details about the transition. This may be an empty string.\n                        maxLength: 32768\n                        type: string\n                      observedGeneration:\n                        description: observedGeneration represents the .metadata.generation\n                          that the condition was set based upon. For instance, if\n                          .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration\n                          is 9, the condition is out of date with respect to the current\n                          state of the instance.\n                        format: int64\n                        minimum: 0\n                        type: integer\n                      reason:\n                        description: reason contains a programmatic identifier indicating\n                          the reason for the condition's last transition. Producers\n                          of specific condition types may define expected values and\n                          meanings for this field, and whether the values are considered\n                          a guaranteed API. The value should be a CamelCase string.\n                          This field may not be empty.\n                        maxLength: 1024\n                        minLength: 1\n                        pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$\n                        type: string\n                      status:\n                        description: status of the condition, one of True, False,\n                          Unknown.\n                        enum:\n                        - \"True\"\n                        - \"False\"\n                        - Unknown\n                        type: string\n                      type:\n                        description: type of condition in CamelCase or in foo.example.com/CamelCase.\n                          --- Many .condition.type values are consistent across resources\n                          like Available, but because arbitrary conditions can be\n                          useful (see .node.status.conditions), the ability to deconflict\n                          is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)\n                        maxLength: 316\n                        pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$\n                        type: string\n                    required:\n                    - lastTransitionTime\n                    - message\n                    - reason\n                    - status\n                    - type\n                    type: object\n                  type: array\n                description: LockedResourceStatuses contains the reconcile status\n                  for each of the managed resources\n                type: object\n            type: object\n        type: object\n    served: true\n    storage: true\n    subresources:\n      status: {}\n"
  },
  {
    "path": "config/crd/kustomization.yaml",
    "content": "# This kustomization.yaml is not intended to be run by itself,\n# since it depends on service name and namespace that are out of this kustomize package.\n# It should be run by config/default\nresources:\n- bases/operator-utils.example.io_mycrds.yaml\n- bases/operator-utils.example.io_enforcingcrds.yaml\n- bases/operator-utils.example.io_enforcingpatches.yaml\n- bases/operator-utils.example.io_templatedenforcingcrds.yaml\n# +kubebuilder:scaffold:crdkustomizeresource\n\npatchesStrategicMerge:\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.\n# patches here are for enabling the conversion webhook for each CRD\n#- patches/webhook_in_mycrds.yaml\n#- patches/webhook_in_enforcingcrds.yaml\n#- patches/webhook_in_enforcingpatches.yaml\n#- patches/webhook_in_templatedenforcingcrds.yaml\n# +kubebuilder:scaffold:crdkustomizewebhookpatch\n\n# [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix.\n# patches here are for enabling the CA injection for each CRD\n#- patches/cainjection_in_mycrds.yaml\n#- patches/cainjection_in_enforcingcrds.yaml\n#- patches/cainjection_in_enforcingpatches.yaml\n#- patches/cainjection_in_templatedenforcingcrds.yaml\n# +kubebuilder:scaffold:crdkustomizecainjectionpatch\n\n# the following config is for teaching kustomize how to do kustomization for CRDs.\nconfigurations:\n- kustomizeconfig.yaml\n"
  },
  {
    "path": "config/crd/kustomizeconfig.yaml",
    "content": "# This file is for teaching kustomize how to substitute name and namespace reference in CRD\nnameReference:\n- kind: Service\n  version: v1\n  fieldSpecs:\n  - kind: CustomResourceDefinition\n    group: apiextensions.k8s.io\n    path: spec/conversion/webhookClientConfig/service/name\n\nnamespace:\n- kind: CustomResourceDefinition\n  group: apiextensions.k8s.io\n  path: spec/conversion/webhookClientConfig/service/namespace\n  create: false\n\nvarReference:\n- path: metadata/annotations\n"
  },
  {
    "path": "config/crd/patches/cainjection_in_enforcingcrds.yaml",
    "content": "# The following patch adds a directive for certmanager to inject CA into the CRD\n# CRD conversion requires k8s 1.13 or later.\napiVersion: apiextensions.k8s.io/v1beta1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)\n  name: enforcingcrds.operator-utils.example.io\n"
  },
  {
    "path": "config/crd/patches/cainjection_in_enforcingpatches.yaml",
    "content": "# The following patch adds a directive for certmanager to inject CA into the CRD\n# CRD conversion requires k8s 1.13 or later.\napiVersion: apiextensions.k8s.io/v1beta1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)\n  name: enforcingpatches.operator-utils.example.io\n"
  },
  {
    "path": "config/crd/patches/cainjection_in_mycrds.yaml",
    "content": "# The following patch adds a directive for certmanager to inject CA into the CRD\n# CRD conversion requires k8s 1.13 or later.\napiVersion: apiextensions.k8s.io/v1beta1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)\n  name: mycrds.operator-utils.example.io\n"
  },
  {
    "path": "config/crd/patches/cainjection_in_templatedenforcingcrds.yaml",
    "content": "# The following patch adds a directive for certmanager to inject CA into the CRD\n# CRD conversion requires k8s 1.13 or later.\napiVersion: apiextensions.k8s.io/v1beta1\nkind: CustomResourceDefinition\nmetadata:\n  annotations:\n    cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)\n  name: templatedenforcingcrds.operator-utils.example.io\n"
  },
  {
    "path": "config/crd/patches/webhook_in_enforcingcrds.yaml",
    "content": "# The following patch enables conversion webhook for CRD\n# CRD conversion requires k8s 1.13 or later.\napiVersion: apiextensions.k8s.io/v1beta1\nkind: CustomResourceDefinition\nmetadata:\n  name: enforcingcrds.operator-utils.example.io\nspec:\n  conversion:\n    strategy: Webhook\n    webhookClientConfig:\n      # this is \"\\n\" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,\n      # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)\n      caBundle: Cg==\n      service:\n        namespace: system\n        name: webhook-service\n        path: /convert\n"
  },
  {
    "path": "config/crd/patches/webhook_in_enforcingpatches.yaml",
    "content": "# The following patch enables conversion webhook for CRD\n# CRD conversion requires k8s 1.13 or later.\napiVersion: apiextensions.k8s.io/v1beta1\nkind: CustomResourceDefinition\nmetadata:\n  name: enforcingpatches.operator-utils.example.io\nspec:\n  conversion:\n    strategy: Webhook\n    webhookClientConfig:\n      # this is \"\\n\" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,\n      # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)\n      caBundle: Cg==\n      service:\n        namespace: system\n        name: webhook-service\n        path: /convert\n"
  },
  {
    "path": "config/crd/patches/webhook_in_mycrds.yaml",
    "content": "# The following patch enables conversion webhook for CRD\n# CRD conversion requires k8s 1.13 or later.\napiVersion: apiextensions.k8s.io/v1beta1\nkind: CustomResourceDefinition\nmetadata:\n  name: mycrds.operator-utils.example.io\nspec:\n  conversion:\n    strategy: Webhook\n    webhookClientConfig:\n      # this is \"\\n\" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,\n      # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)\n      caBundle: Cg==\n      service:\n        namespace: system\n        name: webhook-service\n        path: /convert\n"
  },
  {
    "path": "config/crd/patches/webhook_in_templatedenforcingcrds.yaml",
    "content": "# The following patch enables conversion webhook for CRD\n# CRD conversion requires k8s 1.13 or later.\napiVersion: apiextensions.k8s.io/v1beta1\nkind: CustomResourceDefinition\nmetadata:\n  name: templatedenforcingcrds.operator-utils.example.io\nspec:\n  conversion:\n    strategy: Webhook\n    webhookClientConfig:\n      # this is \"\\n\" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,\n      # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)\n      caBundle: Cg==\n      service:\n        namespace: system\n        name: webhook-service\n        path: /convert\n"
  },
  {
    "path": "config/default/kustomization.yaml",
    "content": "# Adds namespace to all resources.\nnamespace: operator-utils\n\n# Value of this field is prepended to the\n# names of all resources, e.g. a deployment named\n# \"wordpress\" becomes \"alices-wordpress\".\n# Note that it should also match with the prefix (text before '-') of the namespace\n# field above.\nnamePrefix: operator-utils-\n\n# Labels to add to all resources and selectors.\n#commonLabels:\n#  someName: someValue\n\nbases:\n- ../crd\n- ../rbac\n- ../manager\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in\n# crd/kustomization.yaml\n#- ../webhook\n# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.\n#- ../certmanager\n# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.\n- ../prometheus\n\npatchesStrategicMerge:\n  # Protect the /metrics endpoint by putting it behind auth.\n  # If you want your controller-manager to expose the /metrics\n  # endpoint w/o any authn/z, please comment the following line.\n- manager_auth_proxy_patch.yaml\n\n# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in\n# crd/kustomization.yaml\n#- manager_webhook_patch.yaml\n\n# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'.\n# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks.\n# 'CERTMANAGER' needs to be enabled to use ca injection\n#- webhookcainjection_patch.yaml\n\n# the following config is for teaching kustomize how to do var substitution\nvars:\n\n- name: METRICS_SERVICE_NAME\n  objref:\n    kind: Service\n    version: v1\n    name: controller-manager-metrics\n- name: METRICS_SERVICE_NAMESPACE\n  objref:\n    kind: Service\n    version: v1\n    name: controller-manager-metrics\n  fieldref:\n    fieldpath: metadata.namespace\n\n# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.\n#- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR\n#  objref:\n#    kind: Certificate\n#    group: cert-manager.io\n#    version: v1alpha2\n#    name: serving-cert # this name should match the one in certificate.yaml\n#  fieldref:\n#    fieldpath: metadata.namespace\n#- name: CERTIFICATE_NAME\n#  objref:\n#    kind: Certificate\n#    group: cert-manager.io\n#    version: v1alpha2\n#    name: serving-cert # this name should match the one in certificate.yaml\n#- name: SERVICE_NAMESPACE # namespace of the service\n#  objref:\n#    kind: Service\n#    version: v1\n#    name: webhook-service\n#  fieldref:\n#    fieldpath: metadata.namespace\n#- name: SERVICE_NAME\n#  objref:\n#    kind: Service\n#    version: v1\n#    name: webhook-service\n"
  },
  {
    "path": "config/default/manager_auth_proxy_patch.yaml",
    "content": "# This patch inject a sidecar container which is a HTTP proxy for the \n# controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews.\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: controller-manager\n  namespace: system\nspec:\n  template:\n    spec:\n      containers:\n      - name: kube-rbac-proxy\n        image: quay.io/coreos/kube-rbac-proxy:v0.11.0\n        args:\n        - \"--secure-listen-address=0.0.0.0:8443\"\n        - \"--upstream=http://127.0.0.1:8080/\"\n        - \"--logtostderr=true\"\n        - \"--v=0\"\n        - \"--tls-cert-file=/etc/certs/tls/tls.crt\"\n        - \"--tls-private-key-file=/etc/certs/tls/tls.key\"\n        volumeMounts:\n          - mountPath: /etc/certs/tls\n            name: tls-cert         \n        ports:\n        - containerPort: 8443\n          name: https\n        resources:\n          limits:\n            cpu: 500m\n            memory: 128Mi\n          requests:\n            cpu: 5m\n            memory: 64Mi\n      - name: manager\n        args:\n        - \"--metrics-addr=127.0.0.1:8080\"\n        - \"--enable-leader-election\"\n      volumes:\n      - name: tls-cert\n        secret:\n          defaultMode: 420\n          secretName: operator-utils-operator-certs          \n"
  },
  {
    "path": "config/default/manager_webhook_patch.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: controller-manager\n  namespace: system\nspec:\n  template:\n    spec:\n      containers:\n      - name: manager\n        ports:\n        - containerPort: 9443\n          name: webhook-server\n          protocol: TCP\n        volumeMounts:\n        - mountPath: /tmp/k8s-webhook-server/serving-certs\n          name: cert\n          readOnly: true\n      volumes:\n      - name: cert\n        secret:\n          defaultMode: 420\n          secretName: webhook-server-cert\n"
  },
  {
    "path": "config/default/webhookcainjection_patch.yaml",
    "content": "# This patch add annotation to admission webhook config and\n# the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize.\napiVersion: admissionregistration.k8s.io/v1beta1\nkind: MutatingWebhookConfiguration\nmetadata:\n  name: mutating-webhook-configuration\n  annotations:\n    cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)\n---\napiVersion: admissionregistration.k8s.io/v1beta1\nkind: ValidatingWebhookConfiguration\nmetadata:\n  name: validating-webhook-configuration\n  annotations:\n    cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)\n"
  },
  {
    "path": "config/helmchart/.helmignore",
    "content": "# Patterns to ignore when building packages.\n# This supports shell glob matching, relative path matching, and\n# negation (prefixed with !). Only one pattern per line.\n.DS_Store\n# Common VCS dirs\n.git/\n.gitignore\n.bzr/\n.bzrignore\n.hg/\n.hgignore\n.svn/\n# Common backup files\n*.swp\n*.bak\n*.tmp\n*.orig\n*~\n# Various IDEs\n.project\n.idea/\n*.tmproj\n.vscode/\n"
  },
  {
    "path": "config/helmchart/Chart.yaml.tpl",
    "content": "apiVersion: v1\nname: operator-utils\nversion: ${version}\nappVersion: ${version}\ndescription: Helm chart that deploys operator-utils\nkeywords:\n  - volume\n  - storage\n  - csi\n  - expansion\n  - monitoring\nsources:\n  - https://github.com/redhat-cop/operator-utils\nengine: gotpl"
  },
  {
    "path": "config/helmchart/kustomization.yaml",
    "content": "# Adds namespace to all resources.\nnamespace: release-namespace\n\n# Value of this field is prepended to the\n# names of all resources, e.g. a deployment named\n# \"wordpress\" becomes \"alices-wordpress\".\n# Note that it should also match with the prefix (text before '-') of the namespace\n# field above.\nnamePrefix: operator-utils-\n\n# Labels to add to all resources and selectors.\n#commonLabels:\n#  someName: someValue\n\nbases:\n- ../rbac\n- ../prometheus\n\nvars:\n- name: METRICS_SERVICE_NAME\n  objref:\n    kind: Service\n    version: v1\n    name: controller-manager-metrics\n- name: METRICS_SERVICE_NAMESPACE\n  objref:\n    kind: Service\n    version: v1\n    name: controller-manager-metrics\n  fieldref:\n    fieldpath: metadata.namespace\n"
  },
  {
    "path": "config/helmchart/templates/_helpers.tpl",
    "content": "{{/* vim: set filetype=mustache: */}}\n{{/*\nExpand the name of the chart.\n*/}}\n{{- define \"operator-utils.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 \"operator-utils.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{{/*\nCreate chart name and version as used by the chart label.\n*/}}\n{{- define \"operator-utils.chart\" -}}\n{{- printf \"%s-%s\" .Chart.Name .Chart.Version | replace \"+\" \"_\" | trunc 63 | trimSuffix \"-\" }}\n{{- end }}\n\n{{/*\nCommon labels\n*/}}\n{{- define \"operator-utils.labels\" -}}\nhelm.sh/chart: {{ include \"operator-utils.chart\" . }}\n{{ include \"operator-utils.selectorLabels\" . }}\n{{- if .Chart.AppVersion }}\napp.kubernetes.io/version: {{ .Chart.AppVersion | quote }}\n{{- end }}\napp.kubernetes.io/managed-by: {{ .Release.Service }}\n{{- end }}\n\n{{/*\nSelector labels\n*/}}\n{{- define \"operator-utils.selectorLabels\" -}}\napp.kubernetes.io/name: {{ include \"operator-utils.name\" . }}\napp.kubernetes.io/instance: {{ .Release.Name }}\n{{- end }}\n\n{{/*\nCreate the name of the service account to use\n*/}}\n{{- define \"operator-utils.serviceAccountName\" -}}\n{{- if .Values.serviceAccount.create }}\n{{- default (include \"operator-utils.fullname\" .) .Values.serviceAccount.name }}\n{{- else }}\n{{- default \"default\" .Values.serviceAccount.name }}\n{{- end }}\n{{- end }}\n"
  },
  {
    "path": "config/helmchart/templates/manager.yaml",
    "content": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ include \"operator-utils.fullname\" . }}\n  labels:\n    {{- include \"operator-utils.labels\" . | nindent 4 }}\n    operator: operator-utils-operator\nspec:\n  selector:\n    matchLabels:\n      {{- include \"operator-utils.selectorLabels\" . | nindent 6 }}\n  replicas: {{ .Values.replicaCount }}\n  template:\n    metadata:\n    {{- with .Values.podAnnotations }}\n      annotations:\n        {{- toYaml . | nindent 8 }}\n    {{- end }}\n      labels:\n        {{- include \"operator-utils.selectorLabels\" . | nindent 8 }}\n    spec:\n      {{- with .Values.imagePullSecrets }}\n      imagePullSecrets:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n      serviceAccountName: operator-utils-controller-manager    \n      containers:\n      - args:\n        - --secure-listen-address=0.0.0.0:8443\n        - --upstream=http://127.0.0.1:8080/\n        - --logtostderr=true\n        - --tls-cert-file=/etc/certs/tls/tls.crt\n        - --tls-private-key-file=/etc/certs/tls/tls.key\n        - --v=10\n        image: \"{{ .Values.kube_rbac_proxy.image.repository }}:{{ .Values.kube_rbac_proxy.image.tag }}\"\n        name: kube-rbac-proxy\n        ports:\n        - containerPort: 8443\n          name: https\n        volumeMounts:\n        - mountPath: /etc/certs/tls\n          name: tls-cert\n        imagePullPolicy: {{ .Values.kube_rbac_proxy.image.pullPolicy }}\n        resources:\n          {{- toYaml .Values.kube_rbac_proxy.resources | nindent 10 }}      \n      - command:\n        - /manager\n        args:\n        - --enable-leader-election\n        image: \"{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}\"\n        imagePullPolicy: {{ .Values.image.pullPolicy }}\n        name: {{ .Chart.Name }}\n        resources:\n          {{- toYaml .Values.resources | nindent 12 }}\n      {{- with .Values.nodeSelector }}\n      nodeSelector:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n      {{- with .Values.affinity }}\n      affinity:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n      {{- with .Values.tolerations }}\n      tolerations:\n        {{- toYaml . | nindent 8 }}\n      {{- end }}\n      volumes:\n      - name: tls-cert\n        secret:\n          defaultMode: 420\n          secretName: operator-utils-operator-certs "
  },
  {
    "path": "config/helmchart/values.yaml.tpl",
    "content": "# Default values for helm-try.\n# This is a YAML-formatted file.\n# Declare variables to be passed into your templates.\n\nreplicaCount: 1\n\nimage:\n  repository: ${image_repo}\n  pullPolicy: IfNotPresent\n  # Overrides the image tag whose default is the chart appVersion.\n  tag: v${version}\n\nimagePullSecrets: []\nnameOverride: \"\"\nfullnameOverride: \"\"\n\nserviceAccount:\n  # Specifies whether a service account should be created\n  create: true\n  # Annotations to add to the service account\n  annotations: {}\n  # The name of the service account to use.\n  # If not set and create is true, a name is generated using the fullname template\n  name: \"\"\n\npodAnnotations: {}\n\nresources:\n  limits:\n    cpu: 100m\n    memory: 30Mi\n  requests:\n    cpu: 100m\n    memory: 20Mi\n\nnodeSelector: {}\n\ntolerations: []\n\naffinity: {}\n\nkube_rbac_proxy:\n  image:\n    repository: quay.io/coreos/kube-rbac-proxy\n    pullPolicy: IfNotPresent\n    tag: v0.5.0\n  resources:\n    requests:\n      cpu: 100m\n      memory: 20Mi\n"
  },
  {
    "path": "config/local-development/kustomization.yaml",
    "content": "# Adds namespace to all resources.\nnamespace: operator-utils-operator-local\n\n# Value of this field is prepended to the\n# names of all resources, e.g. a deployment named\n# \"wordpress\" becomes \"alices-wordpress\".\n# Note that it should also match with the prefix (text before '-') of the namespace\n# field above.\nnamePrefix: operator-utils-operator-\n\n# Labels to add to all resources and selectors.\n#commonLabels:\n#  someName: someValue\n\nbases:\n- ../rbac"
  },
  {
    "path": "config/manager/kustomization.yaml",
    "content": "resources:\n- manager.yaml\napiVersion: kustomize.config.k8s.io/v1beta1\nkind: Kustomization\nimages:\n- name: controller\n  newName: quay.io/raffaelespazzoli/operator-utils\n  newTag: latest\n"
  },
  {
    "path": "config/manager/manager.yaml",
    "content": "apiVersion: v1\nkind: Namespace\nmetadata:\n  labels:\n    operator: operator-utils-operator\n  name: system\n---\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: controller-manager\n  namespace: system\n  labels:\n    operator: operator-utils-operator\nspec:\n  selector:\n    matchLabels:\n      operator: operator-utils-operator\n  replicas: 1\n  template:\n    metadata:\n      labels:\n        operator: operator-utils-operator\n    spec:\n      serviceAccountName: controller-manager\n      containers:\n      - command:\n        - /manager\n        args:\n        - --enable-leader-election\n        image: controller:latest\n        name: manager\n        resources:\n          limits:\n            cpu: 100m\n            memory: 30Mi\n          requests:\n            cpu: 100m\n            memory: 20Mi\n      terminationGracePeriodSeconds: 10\n"
  },
  {
    "path": "config/manifests/bases/operator-utils.clusterserviceversion.yaml",
    "content": "apiVersion: operators.coreos.com/v1alpha1\nkind: ClusterServiceVersion\nmetadata:\n  annotations:\n    alm-examples: '[]'\n    capabilities: Basic Install\n    operatorframework.io/suggested-namespace: operator-utils-operator\n    operators.operatorframework.io/builder: operator-sdk-v1.2.0\n    operators.operatorframework.io/project_layout: go.kubebuilder.io/v2\n  name: operator-utils.v0.0.0\n  namespace: placeholder\nspec:\n  apiservicedefinitions: {}\n  customresourcedefinitions:\n    owned:\n    - description: EnforcingCRD is the Schema for the enforcingcrds API\n      displayName: Enforcing CRD\n      kind: EnforcingCRD\n      name: enforcingcrds.operator-utils.example.io\n      version: v1alpha1\n    - description: EnforcingPatch is the Schema for the enforcingpatches API\n      displayName: Enforcing Patch\n      kind: EnforcingPatch\n      name: enforcingpatches.operator-utils.example.io\n      version: v1alpha1\n    - description: MyCRD is the Schema for the mycrds API\n      displayName: My CRD\n      kind: MyCRD\n      name: mycrds.operator-utils.example.io\n      version: v1alpha1\n  description: operator-utils\n  displayName: operator-utils\n  icon:\n  - base64data: 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\n    mediatype: image/png\n  install:\n    spec:\n      deployments: null\n    strategy: \"\"\n  installModes:\n  - supported: false\n    type: OwnNamespace\n  - supported: false\n    type: SingleNamespace\n  - supported: false\n    type: MultiNamespace\n  - supported: true\n    type: AllNamespaces\n  keywords:\n  - utils\n  links:\n  - name: Operator Utils\n    url: https://operator-utils.domain\n  maintainers:\n  - email: raffaele.spazzoli@gmail.com'\n    name: '''raffaele spazzoli'\n  maturity: alpha\n  provider:\n    name: Red Hat Community of Practice\n  version: 0.1.0\n"
  },
  {
    "path": "config/manifests/kustomization.yaml",
    "content": "resources:\n- ../default\n- ../samples\n- ../scorecard\n"
  },
  {
    "path": "config/prometheus/kustomization.yaml",
    "content": "resources:\n- monitor.yaml\n\nconfigurations:\n- kustomizeconfig.yaml\n"
  },
  {
    "path": "config/prometheus/kustomizeconfig.yaml",
    "content": "---\nvarReference:\n- path: spec/endpoints/tlsConfig/serverName\n  kind: ServiceMonitor "
  },
  {
    "path": "config/prometheus/monitor.yaml",
    "content": "\n# Prometheus Monitor Service (Metrics)\napiVersion: monitoring.coreos.com/v1\nkind: ServiceMonitor\nmetadata:\n  labels:\n    operator: operator-utils-operator\n  name: controller-manager-metrics-monitor\n  namespace: system\nspec:\n  endpoints:\n    - bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token\n      interval: 30s\n      port: https\n      scheme: https\n      tlsConfig:\n        caFile: /etc/prometheus/configmaps/serving-certs-ca-bundle/service-ca.crt\n        serverName: $(METRICS_SERVICE_NAME).$(METRICS_SERVICE_NAMESPACE).svc\n  selector:\n    matchLabels:\n      operator: operator-utils-operator\n"
  },
  {
    "path": "config/rbac/auth_proxy_client_clusterrole.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: metrics-reader\nrules:\n- nonResourceURLs: [\"/metrics\"]\n  verbs: [\"get\"]\n"
  },
  {
    "path": "config/rbac/auth_proxy_role.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: proxy-role\nrules:\n- apiGroups: [\"authentication.k8s.io\"]\n  resources:\n  - tokenreviews\n  verbs: [\"create\"]\n- apiGroups: [\"authorization.k8s.io\"]\n  resources:\n  - subjectaccessreviews\n  verbs: [\"create\"]\n"
  },
  {
    "path": "config/rbac/auth_proxy_role_binding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: proxy-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: proxy-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "config/rbac/auth_proxy_service.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  labels:\n    operator: operator-utils-operator\n  annotations:\n    service.alpha.openshift.io/serving-cert-secret-name: operator-utils-operator-certs    \n  name: controller-manager-metrics\n  namespace: system\nspec:\n  ports:\n  - name: https\n    port: 8443\n    targetPort: https\n  selector:\n    operator: operator-utils-operator\n"
  },
  {
    "path": "config/rbac/enforcingcrd_editor_role.yaml",
    "content": "# permissions for end users to edit enforcingcrds.\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: enforcingcrd-editor-role\nrules:\n- apiGroups:\n  - operator-utils.example.io\n  resources:\n  - enforcingcrds\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - operator-utils.example.io\n  resources:\n  - enforcingcrds/status\n  verbs:\n  - get\n"
  },
  {
    "path": "config/rbac/enforcingcrd_viewer_role.yaml",
    "content": "# permissions for end users to view enforcingcrds.\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: enforcingcrd-viewer-role\nrules:\n- apiGroups:\n  - operator-utils.example.io\n  resources:\n  - enforcingcrds\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - operator-utils.example.io\n  resources:\n  - enforcingcrds/status\n  verbs:\n  - get\n"
  },
  {
    "path": "config/rbac/enforcingpatch_editor_role.yaml",
    "content": "# permissions for end users to edit enforcingpatches.\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: enforcingpatch-editor-role\nrules:\n- apiGroups:\n  - operator-utils.example.io\n  resources:\n  - enforcingpatches\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - operator-utils.example.io\n  resources:\n  - enforcingpatches/status\n  verbs:\n  - get\n"
  },
  {
    "path": "config/rbac/enforcingpatch_viewer_role.yaml",
    "content": "# permissions for end users to view enforcingpatches.\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: enforcingpatch-viewer-role\nrules:\n- apiGroups:\n  - operator-utils.example.io\n  resources:\n  - enforcingpatches\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - operator-utils.example.io\n  resources:\n  - enforcingpatches/status\n  verbs:\n  - get\n"
  },
  {
    "path": "config/rbac/kustomization.yaml",
    "content": "resources:\n- role.yaml\n- role_binding.yaml\n- leader_election_role.yaml\n- leader_election_role_binding.yaml\n# Comment the following 4 lines if you want to disable\n# the auth proxy (https://github.com/brancz/kube-rbac-proxy)\n# which protects your /metrics endpoint.\n- auth_proxy_service.yaml\n- auth_proxy_role.yaml\n- auth_proxy_role_binding.yaml\n- auth_proxy_client_clusterrole.yaml\n"
  },
  {
    "path": "config/rbac/leader_election_role.yaml",
    "content": "# permissions to do leader election.\napiVersion: rbac.authorization.k8s.io/v1\nkind: Role\nmetadata:\n  name: leader-election-role\nrules:\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete\n- apiGroups:\n  - \"\"\n  resources:\n  - configmaps/status\n  verbs:\n  - get\n  - update\n  - patch\n- apiGroups:\n  - coordination.k8s.io\n  resources:\n  - leases\n  verbs:\n  - get\n  - list\n  - watch\n  - create\n  - update\n  - patch\n  - delete  \n- apiGroups:\n  - \"\"\n  resources:\n  - events\n  verbs:\n  - create\n  - patch\n"
  },
  {
    "path": "config/rbac/leader_election_role_binding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: RoleBinding\nmetadata:\n  name: leader-election-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: Role\n  name: leader-election-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "config/rbac/mycrd_editor_role.yaml",
    "content": "# permissions for end users to edit mycrds.\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: mycrd-editor-role\nrules:\n- apiGroups:\n  - operator-utils.example.io\n  resources:\n  - mycrds\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - operator-utils.example.io\n  resources:\n  - mycrds/status\n  verbs:\n  - get\n"
  },
  {
    "path": "config/rbac/mycrd_viewer_role.yaml",
    "content": "# permissions for end users to view mycrds.\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: mycrd-viewer-role\nrules:\n- apiGroups:\n  - operator-utils.example.io\n  resources:\n  - mycrds\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - operator-utils.example.io\n  resources:\n  - mycrds/status\n  verbs:\n  - get\n"
  },
  {
    "path": "config/rbac/role.yaml",
    "content": "---\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  creationTimestamp: null\n  name: manager-role\nrules:\n- apiGroups:\n  - '*'\n  resources:\n  - '*'\n  verbs:\n  - '*'\n- apiGroups:\n  - operator-utils.example.io\n  resources:\n  - enforcingcrds\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - operator-utils.example.io\n  resources:\n  - enforcingcrds/status\n  verbs:\n  - get\n  - patch\n  - update\n- apiGroups:\n  - operator-utils.example.io\n  resources:\n  - enforcingpatches\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - operator-utils.example.io\n  resources:\n  - enforcingpatches/status\n  verbs:\n  - get\n  - patch\n  - update\n- apiGroups:\n  - operator-utils.example.io\n  resources:\n  - mycrds\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - operator-utils.example.io\n  resources:\n  - mycrds/status\n  verbs:\n  - get\n  - patch\n  - update\n- apiGroups:\n  - operator-utils.example.io\n  resources:\n  - templatedenforcingcrds\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - operator-utils.example.io\n  resources:\n  - templatedenforcingcrds/status\n  verbs:\n  - get\n  - patch\n  - update\n"
  },
  {
    "path": "config/rbac/role_binding.yaml",
    "content": "apiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: manager-rolebinding\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: manager-role\nsubjects:\n- kind: ServiceAccount\n  name: controller-manager\n  namespace: system\n"
  },
  {
    "path": "config/rbac/templatedenforcingcrd_editor_role.yaml",
    "content": "# permissions for end users to edit templatedenforcingcrds.\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: templatedenforcingcrd-editor-role\nrules:\n- apiGroups:\n  - operator-utils.example.io\n  resources:\n  - templatedenforcingcrds\n  verbs:\n  - create\n  - delete\n  - get\n  - list\n  - patch\n  - update\n  - watch\n- apiGroups:\n  - operator-utils.example.io\n  resources:\n  - templatedenforcingcrds/status\n  verbs:\n  - get\n"
  },
  {
    "path": "config/rbac/templatedenforcingcrd_viewer_role.yaml",
    "content": "# permissions for end users to view templatedenforcingcrds.\napiVersion: rbac.authorization.k8s.io/v1\nkind: ClusterRole\nmetadata:\n  name: templatedenforcingcrd-viewer-role\nrules:\n- apiGroups:\n  - operator-utils.example.io\n  resources:\n  - templatedenforcingcrds\n  verbs:\n  - get\n  - list\n  - watch\n- apiGroups:\n  - operator-utils.example.io\n  resources:\n  - templatedenforcingcrds/status\n  verbs:\n  - get\n"
  },
  {
    "path": "config/samples/kustomization.yaml",
    "content": "## Append samples you want in your CSV to this file as resources ##\nresources:\n- operator-utils_v1alpha1_mycrd.yaml\n- operator-utils_v1alpha1_enforcingcrd.yaml\n- operator-utils_v1alpha1_enforcingpatch.yaml\n- operator-utils_v1alpha1_templatedenforcingcrd.yaml\n# +kubebuilder:scaffold:manifestskustomizesamples\n"
  },
  {
    "path": "config/samples/operator-utils_v1alpha1_enforcingcrd.yaml",
    "content": "apiVersion: operator-utils.example.io/v1alpha1\nkind: EnforcingCRD\nmetadata:\n  name: example-enforcingcrd\nspec:\n  resources:\n    - object: \n        apiVersion: v1\n        kind: ConfigMap\n        metadata:\n          creationTimestamp: \"2020-03-30T16:24:08Z\"\n          name: test-configmap\n          namespace: test-enforcingcrd\n        data:\n          ciao: ciao\n    - object:\n        apiVersion: route.openshift.io/v1\n        kind: Route\n        metadata:\n          name: test-route\n          namespace: test-enforcingcrd\n        spec:\n          host: grafana-istio-system.apps.cluster-4cac.sandbox456.opentlc.com\n          tls:\n            termination: reencrypt\n          to:\n            kind: Service\n            name: grafana\n            weight: 100\n          wildcardPolicy: None    \n"
  },
  {
    "path": "config/samples/operator-utils_v1alpha1_enforcingpatch.yaml",
    "content": "apiVersion: operator-utils.example.io/v1alpha1\nkind: EnforcingPatch\nmetadata:\n  name: test-field-patch\nspec:\n  patches:\n  - id: ciao1\n    targetObjectRef:\n      apiVersion: v1\n      kind: ServiceAccount\n      name: test\n      namespace: test-enforcing-patch\n    patchTemplate: |\n      metadata:\n        annotations:\n          {{ (index . 0) }}: {{ (index . 1) }}\n    patchType: application/strategic-merge-patch+json\n    sourceObjectRefs:\n    - apiVersion: v1\n      kind: Namespace\n      name: default\n      fieldPath: $.metadata.uid\n    - apiVersion: v1\n      kind: ServiceAccount\n      name: default\n      namespace: default\n      fieldPath: $.metadata.uid  "
  },
  {
    "path": "config/samples/operator-utils_v1alpha1_mycrd.yaml",
    "content": "apiVersion: operator-utils.example.io/v1alpha1\nkind: MyCRD\nmetadata:\n  name: example-mycrd\nspec:\n  # Add fields here\n  initialized: false\n  valid: true\n  error: false\n"
  },
  {
    "path": "config/samples/operator-utils_v1alpha1_templatedenforcingcrd.yaml",
    "content": "apiVersion: operator-utils.example.io/v1alpha1\nkind: TemplatedEnforcingCRD\nmetadata:\n  name: example-enforcingcrd\nspec:\n  templates:\n    - objectTemplate: | \n        apiVersion: v1\n        kind: ConfigMap\n        metadata:\n          creationTimestamp: \"2020-03-30T16:24:08Z\"\n          name: test-configmap\n          namespace: {{ .Namespace }}\n        data:\n          ciao: ciao\n    - objectTemplate: |\n        apiVersion: route.openshift.io/v1\n        kind: Route\n        metadata:\n          name: test-route\n          namespace: {{ .Namespace }}\n        spec:\n          host: grafana-istio-system.apps.cluster-4cac.sandbox456.opentlc.com\n          tls:\n            termination: reencrypt\n          to:\n            kind: Service\n            name: grafana\n            weight: 100\n          wildcardPolicy: None"
  },
  {
    "path": "config/scorecard/bases/config.yaml",
    "content": "apiVersion: scorecard.operatorframework.io/v1alpha3\nkind: Configuration\nmetadata:\n  name: config\nstages:\n- parallel: true\n  tests: []\n"
  },
  {
    "path": "config/scorecard/kustomization.yaml",
    "content": "resources:\n- bases/config.yaml\npatchesJson6902:\n- path: patches/basic.config.yaml\n  target:\n    group: scorecard.operatorframework.io\n    version: v1alpha3\n    kind: Configuration\n    name: config\n- path: patches/olm.config.yaml\n  target:\n    group: scorecard.operatorframework.io\n    version: v1alpha3\n    kind: Configuration\n    name: config\n# +kubebuilder:scaffold:patchesJson6902\n"
  },
  {
    "path": "config/scorecard/patches/basic.config.yaml",
    "content": "- op: add\n  path: /stages/0/tests/-\n  value:\n    entrypoint:\n    - scorecard-test\n    - basic-check-spec\n    image: quay.io/operator-framework/scorecard-test:v1.9.0\n    labels:\n      suite: basic\n      test: basic-check-spec-test\n"
  },
  {
    "path": "config/scorecard/patches/olm.config.yaml",
    "content": "- op: add\n  path: /stages/0/tests/-\n  value:\n    entrypoint:\n    - scorecard-test\n    - olm-bundle-validation\n    image: quay.io/operator-framework/scorecard-test:v1.9.0\n    labels:\n      suite: olm\n      test: olm-bundle-validation-test\n- op: add\n  path: /stages/0/tests/-\n  value:\n    entrypoint:\n    - scorecard-test\n    - olm-crds-have-validation\n    image: quay.io/operator-framework/scorecard-test:v1.9.0\n    labels:\n      suite: olm\n      test: olm-crds-have-validation-test\n- op: add\n  path: /stages/0/tests/-\n  value:\n    entrypoint:\n    - scorecard-test\n    - olm-crds-have-resources\n    image: quay.io/operator-framework/scorecard-test:v1.9.0\n    labels:\n      suite: olm\n      test: olm-crds-have-resources-test\n- op: add\n  path: /stages/0/tests/-\n  value:\n    entrypoint:\n    - scorecard-test\n    - olm-spec-descriptors\n    image: quay.io/operator-framework/scorecard-test:v1.9.0\n    labels:\n      suite: olm\n      test: olm-spec-descriptors-test\n- op: add\n  path: /stages/0/tests/-\n  value:\n    entrypoint:\n    - scorecard-test\n    - olm-status-descriptors\n    image: quay.io/operator-framework/scorecard-test:v1.9.0\n    labels:\n      suite: olm\n      test: olm-status-descriptors-test\n"
  },
  {
    "path": "config/webhook/kustomization.yaml",
    "content": "resources:\n- manifests.yaml\n- service.yaml\n\nconfigurations:\n- kustomizeconfig.yaml\n"
  },
  {
    "path": "config/webhook/kustomizeconfig.yaml",
    "content": "# the following config is for teaching kustomize where to look at when substituting vars.\n# It requires kustomize v2.1.0 or newer to work properly.\nnameReference:\n- kind: Service\n  version: v1\n  fieldSpecs:\n  - kind: MutatingWebhookConfiguration\n    group: admissionregistration.k8s.io\n    path: webhooks/clientConfig/service/name\n  - kind: ValidatingWebhookConfiguration\n    group: admissionregistration.k8s.io\n    path: webhooks/clientConfig/service/name\n\nnamespace:\n- kind: MutatingWebhookConfiguration\n  group: admissionregistration.k8s.io\n  path: webhooks/clientConfig/service/namespace\n  create: true\n- kind: ValidatingWebhookConfiguration\n  group: admissionregistration.k8s.io\n  path: webhooks/clientConfig/service/namespace\n  create: true\n\nvarReference:\n- path: metadata/annotations\n"
  },
  {
    "path": "config/webhook/service.yaml",
    "content": "\napiVersion: v1\nkind: Service\nmetadata:\n  name: webhook-service\n  namespace: system\nspec:\n  ports:\n    - port: 443\n      targetPort: 9443\n  selector:\n    control-plane: gitwebhook-operator\n"
  },
  {
    "path": "controllers/enforcingcrd_controller.go",
    "content": "/*\n\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controllers\n\nimport (\n\t\"context\"\n\n\t\"github.com/go-logr/logr\"\n\t\"github.com/scylladb/go-set/strset\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/handler\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\t\"sigs.k8s.io/controller-runtime/pkg/source\"\n\n\t\"github.com/redhat-cop/operator-utils/api/v1alpha1\"\n\toperatorutilsv1alpha1 \"github.com/redhat-cop/operator-utils/api/v1alpha1\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller/lockedpatch\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller/lockedresource\"\n)\n\n// EnforcingCRDReconciler reconciles a EnforcingCRD object\ntype EnforcingCRDReconciler struct {\n\tlockedresourcecontroller.EnforcingReconciler\n\tLog logr.Logger\n}\n\n// +kubebuilder:rbac:groups=operator-utils.example.io,resources=enforcingcrds,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=operator-utils.example.io,resources=enforcingcrds/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=*,resources=*,verbs=*\n\nfunc (r *EnforcingCRDReconciler) Reconcile(context context.Context, req ctrl.Request) (ctrl.Result, error) {\n\tlog := r.Log.WithValues(\"enforcingcrd\", req.NamespacedName)\n\n\t// Fetch the EnforcingCRD instance\n\tinstance := &v1alpha1.EnforcingCRD{}\n\terr := r.GetClient().Get(context, req.NamespacedName, instance)\n\tif err != nil {\n\t\tif errors.IsNotFound(err) {\n\t\t\t// Request object not found, could have been deleted after reconcile request.\n\t\t\t// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.\n\t\t\t// Return and don't requeue\n\t\t\treturn reconcile.Result{}, nil\n\t\t}\n\t\t// Error reading the object - requeue the request.\n\t\treturn reconcile.Result{}, err\n\t}\n\tif ok := r.IsInitialized(instance); !ok {\n\t\terr := r.GetClient().Update(context, instance)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to update instance\", \"instance\", instance)\n\t\t\treturn r.ManageError(context, instance, err)\n\t\t}\n\t\treturn reconcile.Result{}, nil\n\t}\n\n\tif util.IsBeingDeleted(instance) {\n\t\tif !util.HasFinalizer(instance, controllerName) {\n\t\t\treturn reconcile.Result{}, nil\n\t\t}\n\t\terr := r.manageCleanUpLogic(instance)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to delete instance\", \"instance\", instance)\n\t\t\treturn r.ManageError(context, instance, err)\n\t\t}\n\t\tutil.RemoveFinalizer(instance, controllerName)\n\t\terr = r.GetClient().Update(context, instance)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to update instance\", \"instance\", instance)\n\t\t\treturn r.ManageError(context, instance, err)\n\t\t}\n\t\treturn reconcile.Result{}, nil\n\t}\n\n\tlockedResources, err := lockedresource.GetLockedResources(instance.Spec.Resources)\n\tif err != nil {\n\t\tlog.Error(err, \"unable to get locked resources\")\n\t\treturn r.ManageError(context, instance, err)\n\t}\n\terr = r.UpdateLockedResources(context, instance, lockedResources, []lockedpatch.LockedPatch{})\n\tif err != nil {\n\t\tlog.Error(err, \"unable to update locked resources\")\n\t\treturn r.ManageError(context, instance, err)\n\t}\n\n\treturn r.ManageSuccess(context, instance)\n}\n\nfunc (r *EnforcingCRDReconciler) manageCleanUpLogic(instance *v1alpha1.EnforcingCRD) error {\n\terr := r.Terminate(instance, true)\n\tif err != nil {\n\t\tr.Log.Error(err, \"unable to terminate enforcing reconciler for\", \"instance\", instance)\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// IsInitialized can be used to check if instance is correctly initialized.\n// returns false it isn't.\nfunc (r *EnforcingCRDReconciler) IsInitialized(instance *v1alpha1.EnforcingCRD) bool {\n\tneedsUpdate := false\n\tfor i := range instance.Spec.Resources {\n\t\tcurrentSet := strset.New(instance.Spec.Resources[i].ExcludedPaths...)\n\t\tif !currentSet.IsEqual(strset.Union(lockedresource.DefaultExcludedPathsSet, currentSet)) {\n\t\t\tinstance.Spec.Resources[i].ExcludedPaths = strset.Union(lockedresource.DefaultExcludedPathsSet, currentSet).List()\n\t\t\tneedsUpdate = true\n\t\t}\n\t}\n\tif len(instance.Spec.Resources) > 0 && !util.HasFinalizer(instance, controllerName) {\n\t\tutil.AddFinalizer(instance, controllerName)\n\t\tneedsUpdate = true\n\t}\n\tif len(instance.Spec.Resources) == 0 && util.HasFinalizer(instance, controllerName) {\n\t\tutil.RemoveFinalizer(instance, controllerName)\n\t\tneedsUpdate = true\n\t}\n\treturn !needsUpdate\n}\n\nfunc (r *EnforcingCRDReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&operatorutilsv1alpha1.EnforcingCRD{}).\n\t\tWatchesRawSource(&source.Channel{Source: r.GetStatusChangeChannel()}, &handler.EnqueueRequestForObject{}).\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "controllers/enforcingpatch_controller.go",
    "content": "/*\n\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controllers\n\nimport (\n\t\"context\"\n\n\t\"github.com/go-logr/logr\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/handler\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\t\"sigs.k8s.io/controller-runtime/pkg/source\"\n\n\t\"github.com/redhat-cop/operator-utils/api/v1alpha1\"\n\toperatorutilsv1alpha1 \"github.com/redhat-cop/operator-utils/api/v1alpha1\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller/lockedpatch\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller/lockedresource\"\n)\n\n// EnforcingPatchReconciler reconciles a EnforcingPatch object\ntype EnforcingPatchReconciler struct {\n\tlockedresourcecontroller.EnforcingReconciler\n\tLog logr.Logger\n}\n\n// +kubebuilder:rbac:groups=operator-utils.example.io,resources=enforcingpatches,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=operator-utils.example.io,resources=enforcingpatches/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=*,resources=*,verbs=*\n\nfunc (r *EnforcingPatchReconciler) Reconcile(context context.Context, req ctrl.Request) (ctrl.Result, error) {\n\tlog := r.Log.WithValues(\"enforcingpatch\", req.NamespacedName)\n\n\t// Fetch the EnforcingPatch instance\n\tinstance := &v1alpha1.EnforcingPatch{}\n\terr := r.GetClient().Get(context, req.NamespacedName, instance)\n\tif err != nil {\n\t\tif errors.IsNotFound(err) {\n\t\t\t// Request object not found, could have been deleted after reconcile request.\n\t\t\t// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.\n\t\t\t// Return and don't requeue\n\t\t\treturn reconcile.Result{}, nil\n\t\t}\n\t\t// Error reading the object - requeue the request.\n\t\treturn reconcile.Result{}, err\n\t}\n\n\tif ok := r.IsInitialized(instance); !ok {\n\t\terr := r.GetClient().Update(context, instance)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to update instance\", \"instance\", instance)\n\t\t\treturn r.ManageError(context, instance, err)\n\t\t}\n\t\treturn reconcile.Result{}, nil\n\t}\n\n\tlockedPatches, err := lockedpatch.GetLockedPatches(instance.Spec.Patches, r.GetRestConfig(), log)\n\tif err != nil {\n\t\tlog.Error(err, \"unable to get locked patches\")\n\t\treturn r.ManageError(context, instance, err)\n\t}\n\terr = r.UpdateLockedResources(context, instance, []lockedresource.LockedResource{}, lockedPatches)\n\tif err != nil {\n\t\tlog.Error(err, \"unable to update locked pacthes\")\n\t\treturn r.ManageError(context, instance, err)\n\t}\n\n\treturn r.ManageSuccess(context, instance)\n}\n\n// IsInitialized can be used to check if instance is correctly initialized.\n// returns false it isn't.\nfunc (r *EnforcingPatchReconciler) IsInitialized(instance *v1alpha1.EnforcingPatch) bool {\n\tneedsUpdate := true\n\tfor i, patch := range instance.Spec.Patches {\n\n\t\tif patch.PatchType == \"\" {\n\t\t\tpatch.PatchType = \"application/strategic-merge-patch+json\"\n\t\t\tinstance.Spec.Patches[i] = patch\n\t\t\tneedsUpdate = false\n\t\t}\n\t}\n\treturn needsUpdate\n}\nfunc (r *EnforcingPatchReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&operatorutilsv1alpha1.EnforcingPatch{}).\n\t\tWatchesRawSource(&source.Channel{Source: r.GetStatusChangeChannel()}, &handler.EnqueueRequestForObject{}).\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "controllers/mycrd_controller.go",
    "content": "/*\n\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controllers\n\nimport (\n\t\"context\"\n\t\"errors\"\n\n\t\"github.com/go-logr/logr\"\n\t\"github.com/redhat-cop/operator-utils/api/v1alpha1\"\n\toperatorutilsv1alpha1 \"github.com/redhat-cop/operator-utils/api/v1alpha1\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n)\n\nconst controllerName = \"MyCRD_controller\"\n\n//var log = logf.Log.WithName(controllerName)\n\n// MyCRDReconciler reconciles a MyCRD object\ntype MyCRDReconciler struct {\n\tutil.ReconcilerBase\n\tLog logr.Logger\n}\n\n// +kubebuilder:rbac:groups=operator-utils.example.io,resources=mycrds,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=operator-utils.example.io,resources=mycrds/status,verbs=get;update;patch\n\nfunc (r *MyCRDReconciler) Reconcile(context context.Context, req ctrl.Request) (ctrl.Result, error) {\n\tlog := r.Log.WithValues(\"mycrd\", req.NamespacedName)\n\n\t// Fetch the MyCRD instance\n\tinstance := &v1alpha1.MyCRD{}\n\terr := r.GetClient().Get(context, req.NamespacedName, instance)\n\tif err != nil {\n\t\tif apierrors.IsNotFound(err) {\n\t\t\t// Request object not found, could have been deleted after reconcile request.\n\t\t\t// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.\n\t\t\t// Return and don't requeue\n\t\t\treturn reconcile.Result{}, nil\n\t\t}\n\t\t// Error reading the object - requeue the request.\n\t\treturn reconcile.Result{}, err\n\t}\n\n\tif ok, err := r.IsValid(instance); !ok {\n\t\treturn r.ManageError(context, instance, err)\n\t}\n\n\tif ok := r.IsInitialized(instance); !ok {\n\t\terr := r.GetClient().Update(context, instance)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to update instance\", \"instance\", instance)\n\t\t\treturn r.ManageError(context, instance, err)\n\t\t}\n\t\treturn reconcile.Result{}, nil\n\t}\n\n\tif util.IsBeingDeleted(instance) {\n\t\tif !util.HasFinalizer(instance, controllerName) {\n\t\t\treturn reconcile.Result{}, nil\n\t\t}\n\t\terr := r.manageCleanUpLogic(instance)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to delete instance\", \"instance\", instance)\n\t\t\treturn r.ManageError(context, instance, err)\n\t\t}\n\t\tutil.RemoveFinalizer(instance, controllerName)\n\t\terr = r.GetClient().Update(context, instance)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to update instance\", \"instance\", instance)\n\t\t\treturn r.ManageError(context, instance, err)\n\t\t}\n\t\treturn reconcile.Result{}, nil\n\t}\n\n\terr = r.manageOperatorLogic(instance)\n\tif err != nil {\n\t\treturn r.ManageError(context, instance, err)\n\t}\n\treturn r.ManageSuccess(context, instance)\n}\n\nfunc (r *MyCRDReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&operatorutilsv1alpha1.MyCRD{}).\n\t\tComplete(r)\n}\n\nfunc (r *MyCRDReconciler) IsInitialized(obj metav1.Object) bool {\n\tmycrd, ok := obj.(*v1alpha1.MyCRD)\n\tif !ok {\n\t\treturn false\n\t}\n\tif mycrd.Spec.Initialized {\n\t\treturn true\n\t}\n\tutil.AddFinalizer(mycrd, controllerName)\n\tmycrd.Spec.Initialized = true\n\treturn false\n\n}\n\nfunc (r *MyCRDReconciler) IsValid(obj metav1.Object) (bool, error) {\n\tmycrd, ok := obj.(*v1alpha1.MyCRD)\n\tif !ok {\n\t\treturn false, errors.New(\"not a mycrd object\")\n\t}\n\tif mycrd.Spec.Valid {\n\t\treturn true, nil\n\t}\n\treturn false, errors.New(\"not valid because blah blah\")\n}\n\nfunc (r *MyCRDReconciler) manageCleanUpLogic(mycrd *v1alpha1.MyCRD) error {\n\treturn nil\n}\n\nfunc (r *MyCRDReconciler) manageOperatorLogic(mycrd *v1alpha1.MyCRD) error {\n\tif mycrd.Spec.Error {\n\t\treturn errors.New(\"error because blah blah\")\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "controllers/suite_test.go",
    "content": "/*\n\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controllers\n\nimport (\n\t\"path/filepath\"\n\t\"testing\"\n\n\t. \"github.com/onsi/ginkgo\"\n\t. \"github.com/onsi/gomega\"\n\t\"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/envtest\"\n\n\toperatorutilsv1alpha1 \"github.com/redhat-cop/operator-utils/api/v1alpha1\"\n\t// +kubebuilder:scaffold:imports\n)\n\n// These tests use Ginkgo (BDD-style Go testing framework). Refer to\n// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.\n\nvar cfg *rest.Config\nvar k8sClient client.Client\nvar testEnv *envtest.Environment\n\nfunc TestAPIs(t *testing.T) {\n\tRegisterFailHandler(Fail)\n\n\tRunSpecsWithDefaultAndCustomReporters(t,\n\t\t\"Controller Suite\",\n\t\t[]Reporter{})\n}\n\nvar _ = BeforeSuite(func(done Done) {\n\t//logf.SetLogger(zap.WriteTo(GinkgoWriter))\n\n\tBy(\"bootstrapping test environment\")\n\ttestEnv = &envtest.Environment{\n\t\tCRDDirectoryPaths: []string{filepath.Join(\"..\", \"config\", \"crd\", \"bases\")},\n\t}\n\n\tvar err error\n\tcfg, err = testEnv.Start()\n\tExpect(err).ToNot(HaveOccurred())\n\tExpect(cfg).ToNot(BeNil())\n\n\terr = operatorutilsv1alpha1.AddToScheme(scheme.Scheme)\n\tExpect(err).NotTo(HaveOccurred())\n\n\t// +kubebuilder:scaffold:scheme\n\n\tk8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})\n\tExpect(err).ToNot(HaveOccurred())\n\tExpect(k8sClient).ToNot(BeNil())\n\n\tclose(done)\n}, 60)\n\nvar _ = AfterSuite(func() {\n\tBy(\"tearing down the test environment\")\n\terr := testEnv.Stop()\n\tExpect(err).ToNot(HaveOccurred())\n})\n"
  },
  {
    "path": "controllers/templatedenforcingcrd_controller.go",
    "content": "/*\n\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage controllers\n\nimport (\n\t\"context\"\n\n\t\"github.com/go-logr/logr\"\n\t\"github.com/scylladb/go-set/strset\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/handler\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\t\"sigs.k8s.io/controller-runtime/pkg/source\"\n\n\t\"github.com/redhat-cop/operator-utils/api/v1alpha1\"\n\toperatorutilsv1alpha1 \"github.com/redhat-cop/operator-utils/api/v1alpha1\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller/lockedpatch\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller/lockedresource\"\n)\n\n// TemplatedEnforcingCRDReconciler reconciles a TemplatedEnforcingCRD object\ntype TemplatedEnforcingCRDReconciler struct {\n\tlockedresourcecontroller.EnforcingReconciler\n\tLog logr.Logger\n}\n\n// +kubebuilder:rbac:groups=operator-utils.example.io,resources=templatedenforcingcrds,verbs=get;list;watch;create;update;patch;delete\n// +kubebuilder:rbac:groups=operator-utils.example.io,resources=templatedenforcingcrds/status,verbs=get;update;patch\n// +kubebuilder:rbac:groups=*,resources=*,verbs=*\n\nfunc (r *TemplatedEnforcingCRDReconciler) Reconcile(context context.Context, req ctrl.Request) (ctrl.Result, error) {\n\tlog := r.Log.WithValues(\"templatedenforcingcrd\", req.NamespacedName)\n\n\t// Fetch the TemplatedEnforcingCRD instance\n\tinstance := &v1alpha1.TemplatedEnforcingCRD{}\n\terr := r.GetClient().Get(context, req.NamespacedName, instance)\n\tif err != nil {\n\t\tif errors.IsNotFound(err) {\n\t\t\t// Request object not found, could have been deleted after reconcile request.\n\t\t\t// Owned objects are automatically garbage collected. For additional cleanup logic use finalizers.\n\t\t\t// Return and don't requeue\n\t\t\treturn reconcile.Result{}, nil\n\t\t}\n\t\t// Error reading the object - requeue the request.\n\t\treturn reconcile.Result{}, err\n\t}\n\n\tif ok := r.IsInitialized(instance); !ok {\n\t\terr := r.GetClient().Update(context, instance)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to update instance\", \"instance\", instance)\n\t\t\treturn r.ManageError(context, instance, err)\n\t\t}\n\t\treturn reconcile.Result{}, nil\n\t}\n\n\tif util.IsBeingDeleted(instance) {\n\t\tif !util.HasFinalizer(instance, controllerName) {\n\t\t\treturn reconcile.Result{}, nil\n\t\t}\n\t\terr := r.manageCleanUpLogic(instance)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to delete instance\", \"instance\", instance)\n\t\t\treturn r.ManageError(context, instance, err)\n\t\t}\n\t\tutil.RemoveFinalizer(instance, controllerName)\n\t\terr = r.GetClient().Update(context, instance)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to update instance\", \"instance\", instance)\n\t\t\treturn r.ManageError(context, instance, err)\n\t\t}\n\t\treturn reconcile.Result{}, nil\n\t}\n\n\tlockedResources, err := lockedresource.GetLockedResourcesFromTemplatesWithRestConfig(instance.Spec.Templates, r.GetRestConfig(), instance)\n\tif err != nil {\n\t\tlog.Error(err, \"unable to get locked resources\")\n\t\treturn r.ManageError(context, instance, err)\n\t}\n\terr = r.UpdateLockedResources(context, instance, lockedResources, []lockedpatch.LockedPatch{})\n\tif err != nil {\n\t\tlog.Error(err, \"unable to update locked resources\")\n\t\treturn r.ManageError(context, instance, err)\n\t}\n\n\treturn r.ManageSuccess(context, instance)\n}\n\n// IsInitialized can be used to check if instance is correctly initialized.\n// returns false it isn't.\nfunc (r *TemplatedEnforcingCRDReconciler) IsInitialized(instance *v1alpha1.TemplatedEnforcingCRD) bool {\n\tneedsUpdate := true\n\tfor i := range instance.Spec.Templates {\n\t\tcurrentSet := strset.New(instance.Spec.Templates[i].ExcludedPaths...)\n\t\tif !currentSet.IsEqual(strset.Union(lockedresource.DefaultExcludedPathsSet, currentSet)) {\n\t\t\tinstance.Spec.Templates[i].ExcludedPaths = strset.Union(lockedresource.DefaultExcludedPathsSet, currentSet).List()\n\t\t\tneedsUpdate = false\n\t\t}\n\t}\n\tif len(instance.Spec.Templates) > 0 && !util.HasFinalizer(instance, controllerName) {\n\t\tutil.AddFinalizer(instance, controllerName)\n\t\tneedsUpdate = false\n\t}\n\tif len(instance.Spec.Templates) == 0 && util.HasFinalizer(instance, controllerName) {\n\t\tutil.RemoveFinalizer(instance, controllerName)\n\t\tneedsUpdate = false\n\t}\n\treturn needsUpdate\n}\n\nfunc (r *TemplatedEnforcingCRDReconciler) manageCleanUpLogic(instance *v1alpha1.TemplatedEnforcingCRD) error {\n\terr := r.Terminate(instance, true)\n\tif err != nil {\n\t\tr.Log.Error(err, \"unable to terminate enforcing reconciler for\", \"instance\", instance)\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (r *TemplatedEnforcingCRDReconciler) SetupWithManager(mgr ctrl.Manager) error {\n\treturn ctrl.NewControllerManagedBy(mgr).\n\t\tFor(&operatorutilsv1alpha1.TemplatedEnforcingCRD{}).\n\t\tWatchesRawSource(&source.Channel{Source: r.GetStatusChangeChannel()}, &handler.EnqueueRequestForObject{}).\n\t\tComplete(r)\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/redhat-cop/operator-utils\n\ngo 1.21\n\nrequire (\n\tgithub.com/BurntSushi/toml v1.3.2\n\tgithub.com/Masterminds/sprig/v3 v3.2.3\n\tgithub.com/evanphx/json-patch v5.7.0+incompatible\n\tgithub.com/go-logr/logr v1.2.4\n\tgithub.com/hashicorp/go-multierror v1.1.1\n\tgithub.com/nsf/jsondiff v0.0.0-20230430225905-43f6cf3098c1\n\tgithub.com/onsi/ginkgo v1.16.5\n\tgithub.com/onsi/gomega v1.27.10\n\tgithub.com/pkg/errors v0.9.1\n\tgithub.com/scylladb/go-set v1.0.2\n\tk8s.io/api v0.28.2\n\tk8s.io/apimachinery v0.28.2\n\tk8s.io/client-go v0.28.2\n\tk8s.io/kubectl v0.28.2\n\tsigs.k8s.io/controller-runtime v0.15.2\n\tsigs.k8s.io/yaml v1.3.0\n)\n\nrequire (\n\tgithub.com/Masterminds/goutils v1.1.1 // indirect\n\tgithub.com/Masterminds/semver/v3 v3.2.0 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/cespare/xxhash/v2 v2.2.0 // indirect\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/emicklei/go-restful/v3 v3.9.0 // indirect\n\tgithub.com/evanphx/json-patch/v5 v5.6.0 // indirect\n\tgithub.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect\n\tgithub.com/fsnotify/fsnotify v1.6.0 // indirect\n\tgithub.com/go-errors/errors v1.4.2 // indirect\n\tgithub.com/go-logr/zapr v1.2.4 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.19.6 // indirect\n\tgithub.com/go-openapi/jsonreference v0.20.2 // indirect\n\tgithub.com/go-openapi/swag v0.22.3 // indirect\n\tgithub.com/gogo/protobuf v1.3.2 // indirect\n\tgithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect\n\tgithub.com/golang/protobuf v1.5.3 // indirect\n\tgithub.com/google/gnostic-models v0.6.8 // indirect\n\tgithub.com/google/go-cmp v0.5.9 // indirect\n\tgithub.com/google/gofuzz v1.2.0 // indirect\n\tgithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect\n\tgithub.com/google/uuid v1.3.0 // indirect\n\tgithub.com/hashicorp/errwrap v1.0.0 // indirect\n\tgithub.com/huandu/xstrings v1.3.3 // indirect\n\tgithub.com/imdario/mergo v0.3.12 // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/mailru/easyjson v0.7.7 // indirect\n\tgithub.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect\n\tgithub.com/mitchellh/copystructure v1.0.0 // indirect\n\tgithub.com/mitchellh/reflectwalk v1.0.0 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.2 // indirect\n\tgithub.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/nxadm/tail v1.4.8 // indirect\n\tgithub.com/prometheus/client_golang v1.16.0 // indirect\n\tgithub.com/prometheus/client_model v0.4.0 // indirect\n\tgithub.com/prometheus/common v0.44.0 // indirect\n\tgithub.com/prometheus/procfs v0.10.1 // indirect\n\tgithub.com/shopspring/decimal v1.2.0 // indirect\n\tgithub.com/spf13/cast v1.3.1 // indirect\n\tgithub.com/spf13/pflag v1.0.5 // indirect\n\tgithub.com/xlab/treeprint v1.2.0 // indirect\n\tgo.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect\n\tgo.uber.org/atomic v1.10.0 // indirect\n\tgo.uber.org/multierr v1.11.0 // indirect\n\tgo.uber.org/zap v1.24.0 // indirect\n\tgolang.org/x/crypto v0.11.0 // indirect\n\tgolang.org/x/net v0.13.0 // indirect\n\tgolang.org/x/oauth2 v0.8.0 // indirect\n\tgolang.org/x/sync v0.2.0 // indirect\n\tgolang.org/x/sys v0.10.0 // indirect\n\tgolang.org/x/term v0.10.0 // indirect\n\tgolang.org/x/text v0.11.0 // indirect\n\tgolang.org/x/time v0.3.0 // indirect\n\tgomodules.xyz/jsonpatch/v2 v2.3.0 // indirect\n\tgoogle.golang.org/appengine v1.6.7 // indirect\n\tgoogle.golang.org/protobuf v1.30.0 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tk8s.io/apiextensions-apiserver v0.27.2 // indirect\n\tk8s.io/cli-runtime v0.28.2 // indirect\n\tk8s.io/component-base v0.28.2 // indirect\n\tk8s.io/klog/v2 v2.100.1 // indirect\n\tk8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect\n\tk8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect\n\tsigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect\n\tsigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect\n\tsigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect\n\tsigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=\ngithub.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=\ngithub.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=\ngithub.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=\ngithub.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=\ngithub.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=\ngithub.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=\ngithub.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=\ngithub.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=\ngithub.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=\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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=\ngithub.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=\ngithub.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=\ngithub.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE=\ngithub.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI=\ngithub.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=\ngithub.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=\ngithub.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=\ngithub.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM=\ngithub.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=\ngithub.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA=\ngithub.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI=\ngithub.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=\ngithub.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=\ngithub.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=\ngithub.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=\ngithub.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=\ngithub.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=\ngithub.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=\ngithub.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo=\ngithub.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA=\ngithub.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=\ngithub.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=\ngithub.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=\ngithub.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=\ngithub.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=\ngithub.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=\ngithub.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=\ngithub.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=\ngithub.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=\ngithub.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=\ngithub.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=\ngithub.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=\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.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\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-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=\ngithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=\ngithub.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=\ngithub.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=\ngithub.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=\ngithub.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=\ngithub.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=\ngithub.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=\ngithub.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=\ngithub.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=\ngithub.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=\ngithub.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=\ngithub.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=\ngithub.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=\ngithub.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=\ngithub.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=\ngithub.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=\ngithub.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=\ngithub.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=\ngithub.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=\ngithub.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=\ngithub.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=\ngithub.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=\ngithub.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=\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/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=\ngithub.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=\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/nsf/jsondiff v0.0.0-20230430225905-43f6cf3098c1 h1:dOYG7LS/WK00RWZc8XGgcUTlTxpp3mKhdR2Q9z9HbXM=\ngithub.com/nsf/jsondiff v0.0.0-20230430225905-43f6cf3098c1/go.mod h1:mpRZBD8SJ55OIICQ3iWH0Yz3cjzA61JdqMLoWXeB2+8=\ngithub.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=\ngithub.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=\ngithub.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=\ngithub.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=\ngithub.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=\ngithub.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=\ngithub.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=\ngithub.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=\ngithub.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=\ngithub.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=\ngithub.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=\ngithub.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=\ngithub.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=\ngithub.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=\ngithub.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=\ngithub.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=\ngithub.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=\ngithub.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=\ngithub.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=\ngithub.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=\ngithub.com/scylladb/go-set v1.0.2 h1:SkvlMCKhP0wyyct6j+0IHJkBkSZL+TDzZ4E7f7BCcRE=\ngithub.com/scylladb/go-set v1.0.2/go.mod h1:DkpGd78rljTxKAnTDPFqXSGxvETQnJyuSOQwsHycqfs=\ngithub.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=\ngithub.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=\ngithub.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=\ngithub.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=\ngithub.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=\ngithub.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=\ngithub.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/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 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=\ngithub.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=\ngithub.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=\ngithub.com/stretchr/testify v1.7.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.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=\ngithub.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=\ngithub.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=\ngithub.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=\ngo.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY=\ngo.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=\ngo.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=\ngo.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=\ngo.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=\ngo.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=\ngo.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=\ngo.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=\ngo.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=\ngo.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=\ngo.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=\ngo.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=\ngolang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=\ngolang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=\ngolang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=\ngolang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=\ngolang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=\ngolang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=\ngolang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=\ngolang.org/x/net v0.13.0 h1:Nvo8UFsZ8X3BhAC9699Z1j7XQ3rsZnUUm7jfBEk1ueY=\ngolang.org/x/net v0.13.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=\ngolang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=\ngolang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=\ngolang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=\ngolang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=\ngolang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=\ngolang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=\ngolang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=\ngolang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=\ngolang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=\ngolang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=\ngolang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM=\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=\ngomodules.xyz/jsonpatch/v2 v2.3.0 h1:8NFhfS6gzxNqjLIYnZxg319wZ5Qjnx4m/CcX+Klzazc=\ngomodules.xyz/jsonpatch/v2 v2.3.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=\ngoogle.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=\ngoogle.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=\ngoogle.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=\ngopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=\ngopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=\ngopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nk8s.io/api v0.28.2 h1:9mpl5mOb6vXZvqbQmankOfPIGiudghwCoLl1EYfUZbw=\nk8s.io/api v0.28.2/go.mod h1:RVnJBsjU8tcMq7C3iaRSGMeaKt2TWEUXcpIt/90fjEg=\nk8s.io/apiextensions-apiserver v0.27.2 h1:iwhyoeS4xj9Y7v8YExhUwbVuBhMr3Q4bd/laClBV6Bo=\nk8s.io/apiextensions-apiserver v0.27.2/go.mod h1:Oz9UdvGguL3ULgRdY9QMUzL2RZImotgxvGjdWRq6ZXQ=\nk8s.io/apimachinery v0.28.2 h1:KCOJLrc6gu+wV1BYgwik4AF4vXOlVJPdiqn0yAWWwXQ=\nk8s.io/apimachinery v0.28.2/go.mod h1:RdzF87y/ngqk9H4z3EL2Rppv5jj95vGS/HaFXrLDApU=\nk8s.io/cli-runtime v0.28.2 h1:64meB2fDj10/ThIMEJLO29a1oujSm0GQmKzh1RtA/uk=\nk8s.io/cli-runtime v0.28.2/go.mod h1:bTpGOvpdsPtDKoyfG4EG041WIyFZLV9qq4rPlkyYfDA=\nk8s.io/client-go v0.28.2 h1:DNoYI1vGq0slMBN/SWKMZMw0Rq+0EQW6/AK4v9+3VeY=\nk8s.io/client-go v0.28.2/go.mod h1:sMkApowspLuc7omj1FOSUxSoqjr+d5Q0Yc0LOFnYFJY=\nk8s.io/component-base v0.28.2 h1:Yc1yU+6AQSlpJZyvehm/NkJBII72rzlEsd6MkBQ+G0E=\nk8s.io/component-base v0.28.2/go.mod h1:4IuQPQviQCg3du4si8GpMrhAIegxpsgPngPRR/zWpzc=\nk8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg=\nk8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=\nk8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ=\nk8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM=\nk8s.io/kubectl v0.28.2 h1:fOWOtU6S0smdNjG1PB9WFbqEIMlkzU5ahyHkc7ESHgM=\nk8s.io/kubectl v0.28.2/go.mod h1:6EQWTPySF1fn7yKoQZHYf9TPwIl2AygHEcJoxFekr64=\nk8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk=\nk8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=\nsigs.k8s.io/controller-runtime v0.15.2 h1:9V7b7SDQSJ08IIsJ6CY1CE85Okhp87dyTMNDG0FS7f4=\nsigs.k8s.io/controller-runtime v0.15.2/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk=\nsigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=\nsigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=\nsigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKUdc5jW3t5jwY7Bo7dcRm+tFxT+NfgY0=\nsigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3/go.mod h1:9n16EZKMhXBNSiUC5kSdFQJkdH3zbxS/JoO619G1VAY=\nsigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 h1:W6cLQc5pnqM7vh3b7HvGNfXrJ/xL6BDMS0v1V/HHg5U=\nsigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3/go.mod h1:JWP1Fj0VWGHyw3YUPjXSQnRnrwezrZSrApfX5S0nIag=\nsigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=\nsigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=\nsigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=\nsigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=\n"
  },
  {
    "path": "hack/boilerplate.go.txt",
    "content": "/*\n\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/"
  },
  {
    "path": "main.go",
    "content": "/*\n\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage main\n\nimport (\n\t\"flag\"\n\t\"os\"\n\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tutilruntime \"k8s.io/apimachinery/pkg/util/runtime\"\n\tclientgoscheme \"k8s.io/client-go/kubernetes/scheme\"\n\t_ \"k8s.io/client-go/plugin/pkg/client/auth/gcp\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log/zap\"\n\n\toperatorutilsv1alpha1 \"github.com/redhat-cop/operator-utils/api/v1alpha1\"\n\t\"github.com/redhat-cop/operator-utils/controllers\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller\"\n\t// +kubebuilder:scaffold:imports\n)\n\nvar (\n\tscheme   = runtime.NewScheme()\n\tsetupLog = ctrl.Log.WithName(\"setup\")\n)\n\nfunc init() {\n\tutilruntime.Must(clientgoscheme.AddToScheme(scheme))\n\n\tutilruntime.Must(operatorutilsv1alpha1.AddToScheme(scheme))\n\t// +kubebuilder:scaffold:scheme\n}\n\nfunc main() {\n\tvar metricsAddr string\n\tvar enableLeaderElection bool\n\tflag.StringVar(&metricsAddr, \"metrics-addr\", \":8080\", \"The address the metric endpoint binds to.\")\n\tflag.BoolVar(&enableLeaderElection, \"enable-leader-election\", false,\n\t\t\"Enable leader election for controller manager. \"+\n\t\t\t\"Enabling this will ensure there is only one active controller manager.\")\n\tflag.Parse()\n\n\tctrl.SetLogger(zap.New(zap.UseDevMode(true)))\n\n\tmgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{\n\t\tScheme:                     scheme,\n\t\tMetricsBindAddress:         metricsAddr,\n\t\tPort:                       9443,\n\t\tLeaderElection:             enableLeaderElection,\n\t\tLeaderElectionID:           \"a0942526.example.io\",\n\t\tLeaderElectionResourceLock: \"configmaps\",\n\t})\n\tif err != nil {\n\t\tsetupLog.Error(err, \"unable to start manager\")\n\t\tos.Exit(1)\n\t}\n\n\tif err = (&controllers.MyCRDReconciler{\n\t\tReconcilerBase: util.NewFromManager(mgr, mgr.GetEventRecorderFor(\"MyCRD_controller\")),\n\t\tLog:            ctrl.Log.WithName(\"controllers\").WithName(\"MyCRD\"),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"unable to create controller\", \"controller\", \"MyCRD\")\n\t\tos.Exit(1)\n\t}\n\tif err = (&controllers.EnforcingCRDReconciler{\n\t\tEnforcingReconciler: lockedresourcecontroller.NewFromManager(mgr, \"EnforcingCRD_controller\", true, false),\n\t\tLog:                 ctrl.Log.WithName(\"controllers\").WithName(\"EnforcingCRD\"),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"unable to create controller\", \"controller\", \"EnforcingCRD\")\n\t\tos.Exit(1)\n\t}\n\tif err = (&controllers.EnforcingPatchReconciler{\n\t\tEnforcingReconciler: lockedresourcecontroller.NewFromManager(mgr, \"EnforcingPatch_controller\", true, false),\n\t\tLog:                 ctrl.Log.WithName(\"controllers\").WithName(\"EnforcingPatch\"),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"unable to create controller\", \"controller\", \"EnforcingPatch\")\n\t\tos.Exit(1)\n\t}\n\tif err = (&controllers.TemplatedEnforcingCRDReconciler{\n\t\tEnforcingReconciler: lockedresourcecontroller.NewFromManager(mgr, \"TemplatedEnforcingCRD_controller\", true, false),\n\t\tLog:                 ctrl.Log.WithName(\"controllers\").WithName(\"TemplatedEnforcingCRD\"),\n\t}).SetupWithManager(mgr); err != nil {\n\t\tsetupLog.Error(err, \"unable to create controller\", \"controller\", \"TemplatedEnforcingCRD\")\n\t\tos.Exit(1)\n\t}\n\t// +kubebuilder:scaffold:builder\n\n\tsetupLog.Info(\"starting manager\")\n\tif err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {\n\t\tsetupLog.Error(err, \"problem running manager\")\n\t\tos.Exit(1)\n\t}\n}\n"
  },
  {
    "path": "pkg/util/apis/conditions.go",
    "content": "package apis\n\nimport (\n\t\"sort\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nconst ReconcileError = \"ReconcileError\"\nconst ReconcileErrorReason = \"LastReconcileCycleFailed\"\nconst ReconcileSuccess = \"ReconcileSuccess\"\nconst ReconcileSuccessReason = \"LastReconcileCycleSucceded\"\n\n// ConditionsAware represents a CRD type that has been enabled with metav1.Conditions, it can then benefit of a series of utility methods.\ntype ConditionsAware interface {\n\tGetConditions() []metav1.Condition\n\tSetConditions(conditions []metav1.Condition)\n}\n\n// AddOrReplaceCondition adds or replaces the passed condition in the passed array of conditions\nfunc AddOrReplaceCondition(c metav1.Condition, conditions []metav1.Condition) []metav1.Condition {\n\tfor i, condition := range conditions {\n\t\tif c.Type == condition.Type {\n\t\t\tconditions[i] = c\n\t\t\treturn conditions\n\t\t}\n\t}\n\tconditions = append(conditions, c)\n\treturn conditions\n}\n\n// GetCondition returns the condition with the given type, if it exists. If the condition does not exists it returns false.\nfunc GetCondition(conditionType string, conditions []metav1.Condition) (metav1.Condition, bool) {\n\tfor _, condition := range conditions {\n\t\tif condition.Type == conditionType {\n\t\t\treturn condition, true\n\t\t}\n\t}\n\treturn metav1.Condition{}, false\n}\n\n// GetLastCondition retruns the last condition based on the condition timestamp. if no condition is present it return false.\nfunc GetLastCondition(conditions []metav1.Condition) (metav1.Condition, bool) {\n\tif len(conditions) == 0 {\n\t\treturn metav1.Condition{}, false\n\t}\n\t//we need to make a copy of the slice\n\tcopiedConditions := []metav1.Condition{}\n\tfor _, condition := range conditions {\n\t\tccondition := condition.DeepCopy()\n\t\tcopiedConditions = append(copiedConditions, *ccondition)\n\t}\n\tsort.Slice(copiedConditions, func(i, j int) bool {\n\t\treturn copiedConditions[i].LastTransitionTime.Before(&copiedConditions[j].LastTransitionTime)\n\t})\n\treturn copiedConditions[len(copiedConditions)-1], true\n}\n\nfunc IsErrorCondition(condition metav1.Condition) bool {\n\treturn !(condition.Type == ReconcileSuccess) || (condition.Type == \"Initializing\")\n}\n"
  },
  {
    "path": "pkg/util/apis/key.go",
    "content": "package apis\n\nimport (\n\t\"errors\"\n\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n)\n\nvar log = ctrl.Log.WithName(\"util.api\")\n\n// GetKeyLong return a unique key for a given object in the pattern of <kind>/<apiversion>/<namespace>/<name>\n// namespace can be null\nfunc GetKeyLong(obj metav1.Object) string {\n\trobj, ok := obj.(runtime.Object)\n\tif !ok {\n\t\terr := errors.New(\"unable to conver meta.Object to runtime.Object\")\n\t\tlog.Error(err, \"unable to conver meta.Object to runtime.Object\", \"object\", obj)\n\t\tpanic(err)\n\t}\n\treturn robj.GetObjectKind().GroupVersionKind().GroupVersion().String() + \"/\" + robj.GetObjectKind().GroupVersionKind().Kind + \"/\" + obj.GetNamespace() + \"/\" + obj.GetName()\n}\n\n// GetKeyShort return a unique key for a given object in the pattern of <apiversion>/<namespace>/<name>\n// namespace can be null\nfunc GetKeyShort(obj metav1.Object) string {\n\treturn obj.GetNamespace() + \"/\" + obj.GetName()\n}\n"
  },
  {
    "path": "pkg/util/crud/crudutils.go",
    "content": "package crud\n\nimport (\n\t\"context\"\n\t\"text/template\"\n\n\t\"github.com/redhat-cop/operator-utils/pkg/util/templates\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log\"\n)\n\n// CreateOrUpdateResource creates a resource if it doesn't exist, and updates (overwrites it), if it exist\n// if owner is not nil, the owner field os set\n// if namespace is not \"\", the namespace field of the object is overwritten with the passed value\n// requires a context with log and client\nfunc CreateOrUpdateResource(context context.Context, owner client.Object, namespace string, obj client.Object) error {\n\tlog := log.FromContext(context)\n\tclient := context.Value(\"client\").(client.Client)\n\tif owner != nil {\n\t\t_ = controllerutil.SetControllerReference(owner, obj, client.Scheme())\n\t}\n\tif namespace != \"\" {\n\t\tobj.SetNamespace(namespace)\n\t}\n\n\tobj2 := &unstructured.Unstructured{}\n\tobj2.SetGroupVersionKind(obj.GetObjectKind().GroupVersionKind())\n\n\terr := client.Get(context, types.NamespacedName{\n\t\tNamespace: obj.GetNamespace(),\n\t\tName:      obj.GetName(),\n\t}, obj2)\n\n\tif apierrors.IsNotFound(err) {\n\t\terr = client.Create(context, obj)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to create object\", \"object\", obj)\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t}\n\tif err == nil {\n\t\tobj.SetResourceVersion(obj2.GetResourceVersion())\n\t\terr = client.Update(context, obj)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to update object\", \"object\", obj)\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\n\t}\n\tlog.Error(err, \"unable to lookup object\", \"object\", obj)\n\treturn err\n}\n\n// CreateOrUpdateResources operates as CreateOrUpdate, but on an array of resources\n// requires a context with log and client\nfunc CreateOrUpdateResources(context context.Context, owner client.Object, namespace string, objs []client.Object) error {\n\tfor _, obj := range objs {\n\t\terr := CreateOrUpdateResource(context, owner, namespace, obj)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// CreateOrUpdateUnstructuredResources operates as CreateOrUpdate, but on an array of unstructured.Unstructured\n// requires a context with log and client\nfunc CreateOrUpdateUnstructuredResources(context context.Context, owner client.Object, namespace string, objs []unstructured.Unstructured) error {\n\tfor _, obj := range objs {\n\t\terr := CreateOrUpdateResource(context, owner, namespace, &obj)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// DeleteResourceIfExists deletes an existing resource. It doesn't fail if the resource does not exist\n// requires a context with log and client\nfunc DeleteResourceIfExists(context context.Context, obj client.Object) error {\n\tlog := log.FromContext(context)\n\tclient := context.Value(\"client\").(client.Client)\n\terr := client.Delete(context, obj)\n\tif err != nil && !apierrors.IsNotFound(err) {\n\t\tlog.Error(err, \"unable to delete object \", \"object\", obj)\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// DeleteResourcesIfExist operates like DeleteResources, but on an arrays of resources\n// requires a context with log and client\nfunc DeleteResourcesIfExist(context context.Context, objs []client.Object) error {\n\tfor _, obj := range objs {\n\t\terr := DeleteResourceIfExists(context, obj)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// DeleteUnstructuredResources operates like DeleteResources, but on an arrays of unstructured.Unstructured\n// requires a context with log and client\nfunc DeleteUnstructuredResources(context context.Context, objs []unstructured.Unstructured) error {\n\tfor _, obj := range objs {\n\t\terr := DeleteResourceIfExists(context, &obj)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// CreateResourceIfNotExists create a resource if it doesn't already exists. If the resource exists it is left untouched and the functin does not fails\n// if owner is not nil, the owner field os set\n// if namespace is not \"\", the namespace field of the object is overwritten with the passed value\n// requires a context with log and client\nfunc CreateResourceIfNotExists(context context.Context, owner client.Object, namespace string, obj client.Object) error {\n\tlog := log.FromContext(context)\n\tclient := context.Value(\"client\").(client.Client)\n\tif owner != nil {\n\t\t_ = controllerutil.SetControllerReference(owner, obj, client.Scheme())\n\t}\n\tif namespace != \"\" {\n\t\tobj.SetNamespace(namespace)\n\t}\n\n\terr := client.Create(context, obj)\n\tif err != nil && !apierrors.IsAlreadyExists(err) {\n\t\tlog.Error(err, \"unable to create object \", \"object\", obj)\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// CreateResourcesIfNotExist operates as CreateResourceIfNotExists, but on an array of resources\n// requires a context with log and client\nfunc CreateResourcesIfNotExist(context context.Context, owner client.Object, namespace string, objs []client.Object) error {\n\tfor _, obj := range objs {\n\t\terr := CreateResourceIfNotExists(context, owner, namespace, obj)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// CreateUnstructuredResourcesIfNotExist operates as CreateResourceIfNotExists, but on an array of unstructured.Unstructured\n// requires a context with log and client\nfunc CreateUnstructuredResourcesIfNotExist(context context.Context, owner client.Object, namespace string, objs []unstructured.Unstructured) error {\n\tfor _, obj := range objs {\n\t\terr := CreateResourceIfNotExists(context, owner, namespace, &obj)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// CreateOrUpdateTemplatedResources processes an initialized template expecting an array of objects as a result and the processes them with the CreateOrUpdate function\n// requires a context with log and client\nfunc CreateOrUpdateTemplatedResources(context context.Context, owner client.Object, namespace string, data interface{}, template *template.Template) error {\n\tlog := log.FromContext(context)\n\tobjs, err := templates.ProcessTemplateArray(context, data, template)\n\tif err != nil {\n\t\tlog.Error(err, \"error creating manifest from template\")\n\t\treturn err\n\t}\n\tfor _, obj := range objs {\n\t\terr = CreateOrUpdateResource(context, owner, namespace, &obj)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// CreateIfNotExistTemplatedResources processes an initialized template expecting an array of objects as a result and then processes them with the CreateResourceIfNotExists function\n// requires a context with log and client\nfunc CreateIfNotExistTemplatedResources(context context.Context, owner client.Object, namespace string, data interface{}, template *template.Template) error {\n\tlog := log.FromContext(context)\n\tobjs, err := templates.ProcessTemplateArray(context, data, template)\n\tif err != nil {\n\t\tlog.Error(err, \"error creating manifest from template\")\n\t\treturn err\n\t}\n\tfor _, obj := range objs {\n\t\terr = CreateResourceIfNotExists(context, owner, namespace, &obj)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// DeleteTemplatedResources processes an initialized template expecting an array of objects as a result and then processes them with the Delete function\n// requires a context with log and client\nfunc DeleteTemplatedResources(context context.Context, data interface{}, template *template.Template) error {\n\tlog := log.FromContext(context)\n\tobjs, err := templates.ProcessTemplateArray(context, data, template)\n\tif err != nil {\n\t\tlog.Error(err, \"error creating manifest from template\")\n\t\treturn err\n\t}\n\tfor _, obj := range objs {\n\t\terr = DeleteResourceIfExists(context, &obj)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/util/discoveryclient/discoveryclientutils.go",
    "content": "package discoveryclient\n\nimport (\n\t\"context\"\n\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/client-go/discovery\"\n\t\"k8s.io/client-go/rest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log\"\n)\n\n// GetDiscoveryClient returns a discovery client for the current reconciler\n// needs context with restConfig\nfunc GetDiscoveryClient(context context.Context) (*discovery.DiscoveryClient, error) {\n\trestConfig := context.Value(\"restConfig\").(*rest.Config)\n\treturn discovery.NewDiscoveryClientForConfig(restConfig)\n}\n\n// IsAPIResourceAvailable checks of a give GroupVersionKind is available in the running apiserver\n// needs context with restConfig and log\nfunc IsGVKDefined(context context.Context, GVK schema.GroupVersionKind) (bool, error) {\n\t_, found, err := GetAPIResourceForGVK(context, GVK)\n\treturn found, err\n}\n\nfunc GetAPIResourceForGVK(context context.Context, GVK schema.GroupVersionKind) (apiresource *v1.APIResource, found bool, err error) {\n\tlog := log.FromContext(context)\n\tdiscoveryClient, err := GetDiscoveryClient(context)\n\tif err != nil {\n\t\tlog.Error(err, \"Unable to get discovery client\")\n\t\treturn nil, false, err\n\t}\n\t// Query for known OpenShift API resource to verify it is available\n\tapiResources, err := discoveryClient.ServerResourcesForGroupVersion(GVK.GroupVersion().String())\n\tif err != nil {\n\t\tif apierrors.IsNotFound(err) {\n\t\t\treturn nil, false, nil\n\t\t}\n\t\tlog.Error(err, \"Unable to retrive resources for\", \"GVK\", GVK)\n\t\treturn nil, false, err\n\t}\n\tfor i := range apiResources.APIResources {\n\t\tif apiResources.APIResources[i].Kind == GVK.Kind {\n\t\t\treturn &apiResources.APIResources[i], true, nil\n\t\t}\n\t}\n\treturn nil, false, nil\n}\n\n// IsGVKNamespaced checks whether the passed GVK os namespaced\n// needs context with restConfig and log\nfunc IsGVKNamespaced(context context.Context, GVK schema.GroupVersionKind) (bool, error) {\n\tresource, found, err := GetAPIResourceForGVK(context, GVK)\n\tif err != nil || !found {\n\t\treturn found, err\n\t}\n\treturn resource.Namespaced, nil\n}\n\n// IsUnstructuredDefined checks whether the content of a unstructured is defined in the current cluster\n// needs context with restConfig and log\nfunc IsUnstructuredDefined(context context.Context, obj *unstructured.Unstructured) (bool, error) {\n\treturn IsGVKDefined(context, obj.GroupVersionKind())\n}\n\n// IsUnstructuredDefined checks whether the content of a unstructured is defined in the current cluster\n// needs context with restConfig and log\nfunc IsUnstructuredNamespaced(context context.Context, obj *unstructured.Unstructured) (bool, error) {\n\treturn IsGVKNamespaced(context, obj.GroupVersionKind())\n}\n"
  },
  {
    "path": "pkg/util/dynamicclient/dynamicclientutils.go",
    "content": "package dynamicclient\n\nimport (\n\t\"context\"\n\t\"strings\"\n\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/runtime/schema\"\n\t\"k8s.io/client-go/discovery\"\n\t\"k8s.io/client-go/dynamic\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/util/jsonpath\"\n\t\"sigs.k8s.io/controller-runtime/pkg/cache\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log\"\n)\n\n// GetDynamicClientOnUnstructured returns a dynamic client on an Unstructured type. This client can be further namespaced.\n// needs context with log and restConfig\n// TODO consider refactoring using apimachinery.RESTClientForGVK in controller-runtime\nfunc GetDynamicClientOnUnstructured(context context.Context, obj *unstructured.Unstructured) (dynamic.ResourceInterface, error) {\n\tlog := log.FromContext(context)\n\tapiRes, err := getAPIReourceForGVK(context, obj.GetObjectKind().GroupVersionKind())\n\tif err != nil {\n\t\tlog.Error(err, \"Unable to get apiresource from unstructured\", \"unstructured\", obj)\n\t\treturn nil, err\n\t}\n\tdc, err := GetDynamicClientForAPIResource(context, apiRes)\n\tif err != nil {\n\t\tlog.Error(err, \"Unable to get namespaceable dynamic client from \", \"resource\", apiRes)\n\t\treturn nil, err\n\t}\n\tif apiRes.Namespaced {\n\t\treturn dc.Namespace(obj.GetNamespace()), nil\n\t}\n\treturn dc, nil\n}\n\n// GetDynamicClientOnAPIResource returns a dynamic client on an APIResource. This client can be further namespaced.\n// needs context with log and restConfig\nfunc GetDynamicClientForAPIResource(context context.Context, resource *metav1.APIResource) (dynamic.NamespaceableResourceInterface, error) {\n\treturn getDynamicClientForGVR(context, schema.GroupVersionResource{\n\t\tGroup:    resource.Group,\n\t\tVersion:  resource.Version,\n\t\tResource: resource.Name,\n\t})\n}\n\nfunc getDynamicClientForGVR(context context.Context, gvr schema.GroupVersionResource) (dynamic.NamespaceableResourceInterface, error) {\n\tlog := log.FromContext(context)\n\trestConfig := context.Value(\"restConfig\").(*rest.Config)\n\tintf, err := dynamic.NewForConfig(restConfig)\n\tif err != nil {\n\t\tlog.Error(err, \"Unable to get dynamic client\")\n\t\treturn nil, err\n\t}\n\tres := intf.Resource(gvr)\n\treturn res, nil\n}\n\n// GetDynamicClientForGVK returns a dynamic client on an gvk type. Also returns whether this reosurce is namespaced. This client can be further namespaced.\n// needs context with log and restConfig\nfunc GetDynamicClientForGVK(context context.Context, gvk schema.GroupVersionKind) (dynamic.NamespaceableResourceInterface, bool, error) {\n\tlog := log.FromContext(context)\n\tapiRes, err := getAPIReourceForGVK(context, gvk)\n\tif err != nil {\n\t\tlog.Error(err, \"unable to get apiresource from\", \"gvk\", gvk)\n\t\treturn nil, false, err\n\t}\n\tnri, err := GetDynamicClientForAPIResource(context, apiRes)\n\tif err != nil {\n\t\tlog.Error(err, \"unable to get dynamic client from\", \"apires\", apiRes)\n\t\treturn nil, false, err\n\t}\n\treturn nri, apiRes.Namespaced, nil\n}\n\nfunc getAPIReourceForGVK(context context.Context, gvk schema.GroupVersionKind) (*metav1.APIResource, error) {\n\tres := &metav1.APIResource{}\n\tlog := log.FromContext(context)\n\trestConfig := context.Value(\"restConfig\").(*rest.Config)\n\tdiscoveryClient := discovery.NewDiscoveryClientForConfigOrDie(restConfig)\n\tresList, err := discoveryClient.ServerResourcesForGroupVersion(gvk.GroupVersion().String())\n\tif err != nil {\n\t\tlog.Error(err, \"unable to retrieve resource list for\", \"gvk\", gvk.GroupVersion().String())\n\t\treturn nil, err\n\t}\n\tfor i := range resList.APIResources {\n\t\t//if a resource contains a \"/\" it's referencing a subresource. we don't support subresource for now.\n\t\tif resList.APIResources[i].Kind == gvk.Kind && !strings.Contains(resList.APIResources[i].Name, \"/\") {\n\t\t\tres = &resList.APIResources[i]\n\t\t\tres.Group = gvk.Group\n\t\t\tres.Version = gvk.Version\n\t\t\tbreak\n\t\t}\n\t}\n\treturn res, nil\n}\n\n// SetIndexField this function allows to prepare an index field for an objct so that fieldSelector can be used.\n// It needs a cache object probably obtained via mrg.GetCache()\n// This is a generic implementation, so it's relatively slow\n// path should be expressed in the form of .<field>.<field> ...\n// needs context with log\nfunc SetIndexField(context context.Context, cache cache.Cache, obj client.Object, path string) error {\n\tlog := log.FromContext(context)\n\treturn cache.IndexField(context, obj, path, func(o client.Object) []string {\n\t\tmapObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(o)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to convert object to unstructured \", \"object\", o)\n\t\t\treturn nil\n\t\t}\n\t\tjp := jsonpath.New(\"fieldPath:\" + path)\n\t\terr = jp.Parse(\"{ $\" + path + \"}\")\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to parse \", \"fieldPath\", path)\n\t\t\treturn nil\n\t\t}\n\t\tvalues, err := jp.FindResults(mapObj)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to apply \", \"jsonpath\", jp, \" to obj \", mapObj)\n\t\t\treturn nil\n\t\t}\n\t\tresult := []string{}\n\t\tif len(values) > 0 {\n\t\t\tfor i := range values[0] {\n\t\t\t\tif val, ok := values[0][i].Interface().(string); ok {\n\t\t\t\t\tresult = append(result, val)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn result\n\t})\n}\n"
  },
  {
    "path": "pkg/util/finalizer.go",
    "content": "/*\nCopyright 2019 Red Hat, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage util\n\nimport (\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil\"\n)\n\n// IsBeingDeleted returns whether this object has been requested to be deleted\nfunc IsBeingDeleted(obj client.Object) bool {\n\treturn !obj.GetDeletionTimestamp().IsZero()\n}\n\n// HasFinalizer returns whether this object has the passed finalizer\n// Deprecated use controllerutil.ContainsFinalizer\nfunc HasFinalizer(obj client.Object, finalizer string) bool {\n\treturn controllerutil.ContainsFinalizer(obj, finalizer)\n}\n\n// AddFinalizer adds the passed finalizer this object\n// Deprecated use controllerutil.AddFinalizer\nfunc AddFinalizer(obj client.Object, finalizer string) {\n\tcontrollerutil.AddFinalizer(obj, finalizer)\n}\n\n// RemoveFinalizer removes the passed finalizer from object\n// Deprecated use controllerutil.RemoveFinalizer\nfunc RemoveFinalizer(obj client.Object, finalizer string) {\n\tcontrollerutil.RemoveFinalizer(obj, finalizer)\n}\n"
  },
  {
    "path": "pkg/util/lockedresourcecontroller/enforcing-reconciler.go",
    "content": "package lockedresourcecontroller\n\nimport (\n\t\"context\"\n\t\"sync\"\n\n\t\"github.com/go-logr/logr\"\n\t\"github.com/redhat-cop/operator-utils/api/v1alpha1\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/apis\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller/lockedpatch\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller/lockedresource\"\n\t\"github.com/scylladb/go-set/strset\"\n\t\"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/tools/record\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/event\"\n\t\"sigs.k8s.io/controller-runtime/pkg/manager\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n)\n\n// EnforcingReconciler is a reconciler designed to as a base type to extend for those operators that compute a set of resources that then need to be kept in place (i.e. enforced)\n// the enforcing piece is taken care for, an implementor would just need to take care of the logic that computes the resources to be enforced.\ntype EnforcingReconciler struct {\n\tutil.ReconcilerBase\n\tlockedResourceManagers      map[string]*LockedResourceManager\n\tstatusChange                chan event.GenericEvent\n\tlockedResourceManagersMutex sync.Mutex\n\tclusterWatchers             bool\n\tlog                         logr.Logger\n\treturnOnlyFailingStatuses   bool\n}\n\n// NewEnforcingReconciler creates a new EnforcingReconciler\n// clusterWatcher determines whether the created watchers should be at the cluster level or namespace level.\n// this affects the kind of permissions needed to run the controller\n// also creating multiple namespace level permissions can create performance issue as one watch per object type per namespace is opened to the API server, if in doubt pass true here.\nfunc NewEnforcingReconciler(client client.Client, scheme *runtime.Scheme, restConfig *rest.Config, apireader client.Reader, recorder record.EventRecorder, clusterWatchers bool, returnOnlyFailingStatuses bool) EnforcingReconciler {\n\treturn EnforcingReconciler{\n\t\tReconcilerBase:              util.NewReconcilerBase(client, scheme, restConfig, recorder, apireader),\n\t\tlockedResourceManagers:      map[string]*LockedResourceManager{},\n\t\tstatusChange:                make(chan event.GenericEvent),\n\t\tlockedResourceManagersMutex: sync.Mutex{},\n\t\tclusterWatchers:             clusterWatchers,\n\t\tlog:                         ctrl.Log.WithName(\"enforcing-reconciler\"),\n\t\treturnOnlyFailingStatuses:   returnOnlyFailingStatuses,\n\t}\n}\n\nfunc NewFromManager(mgr manager.Manager, recorderName string, clusterWatchers bool, returnOnlyFailingStatuses bool) EnforcingReconciler {\n\treturn NewEnforcingReconciler(mgr.GetClient(), mgr.GetScheme(), mgr.GetConfig(), mgr.GetAPIReader(), mgr.GetEventRecorderFor(recorderName), clusterWatchers, returnOnlyFailingStatuses)\n}\n\n// GetStatusChangeChannel returns the channel through which status change events can be received\nfunc (er *EnforcingReconciler) GetStatusChangeChannel() <-chan event.GenericEvent {\n\treturn er.statusChange\n}\n\nfunc (er *EnforcingReconciler) removeLockedResourceManager(instance client.Object) {\n\ter.lockedResourceManagersMutex.Lock()\n\tdefer er.lockedResourceManagersMutex.Unlock()\n\tdelete(er.lockedResourceManagers, apis.GetKeyShort(instance))\n}\n\nfunc (er *EnforcingReconciler) getLockedResourceManager(instance client.Object) (*LockedResourceManager, error) {\n\ter.lockedResourceManagersMutex.Lock()\n\tdefer er.lockedResourceManagersMutex.Unlock()\n\tlockedResourceManager, ok := er.lockedResourceManagers[apis.GetKeyShort(instance)]\n\tif !ok {\n\t\tlockedResourceManager, err := NewLockedResourceManager(er.GetRestConfig(), manager.Options{}, instance, er.statusChange, er.clusterWatchers)\n\t\tif err != nil {\n\t\t\ter.log.Error(err, \"unable to create LockedResourceManager\")\n\t\t\treturn &LockedResourceManager{}, err\n\t\t}\n\t\ter.lockedResourceManagers[apis.GetKeyShort(instance)] = &lockedResourceManager\n\t\treturn &lockedResourceManager, nil\n\t}\n\treturn lockedResourceManager, nil\n}\n\n// UpdateLockedResources will do the following:\n//  1. initialize or retrieve the LockedResourceManager related to the passed parent resource\n//  2. compare the currently enforced resources with the one passed as parameters and then\n//     a. return immediately if they are the same\n//     b. restart the LockedResourceManager if they don't match\nfunc (er *EnforcingReconciler) UpdateLockedResources(context context.Context, instance client.Object, lockedResources []lockedresource.LockedResource, lockedPatches []lockedpatch.LockedPatch) error {\n\treturn er.UpdateLockedResourcesWithRestConfig(context, instance, lockedResources, lockedPatches, er.GetRestConfig())\n}\n\n// UpdateLockedResourcesWithRestConfig will do the following:\n//  1. initialize or retrieve the LockedResourceManager related to the passed parent resource\n//  2. compare the currently enforced resources with the one passed as parameters and then\n//     a. return immediately if they are the same\n//     b. restart the LockedResourceManager if they don't match\n//\n// this variant allows passing a rest config\nfunc (er *EnforcingReconciler) UpdateLockedResourcesWithRestConfig(context context.Context, instance client.Object, lockedResources []lockedresource.LockedResource, lockedPatches []lockedpatch.LockedPatch, config *rest.Config) error {\n\tlockedResourceManager, err := er.getLockedResourceManager(instance)\n\tif err != nil {\n\t\ter.log.Error(err, \"unable to get LockedResourceManager\")\n\t\treturn err\n\t}\n\tsameResources, leftDifference, _, _ := lockedResourceManager.IsSameResources(lockedResources)\n\t//the resource in the leftDifference are not necessarily to be deleted, we need to check if the resource has simply been updated maintinign the sam type/namespace/value.\n\ttoBeDeleted := getToBeDeletdResources(lockedResources, leftDifference)\n\tsamePatches, _, _, _ := lockedResourceManager.IsSamePatches(lockedPatches)\n\tif !sameResources || !samePatches {\n\t\terr = er.DeleteUnstructuredResources(context, lockedresource.AsListOfUnstructured(toBeDeleted))\n\t\tif err != nil {\n\t\t\ter.log.Error(err, \"unable to delete unmanaged\", \"resources\", leftDifference)\n\t\t\treturn err\n\t\t}\n\t\terr := lockedResourceManager.Restart(context, lockedResources, lockedPatches, false, config)\n\t\tif err != nil {\n\t\t\ter.log.Error(err, \"unable to restart\", \"manager\", lockedResourceManager)\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc getToBeDeletdResources(neededResources []lockedresource.LockedResource, modifiedResources []lockedresource.LockedResource) []lockedresource.LockedResource {\n\tneededResourceSet := strset.New()\n\tmodifiedResourcesSet := strset.New()\n\tmodifiedResourceMap := map[string]lockedresource.LockedResource{}\n\ttoBeDeleted := []lockedresource.LockedResource{}\n\tfor _, lockerResource := range neededResources {\n\t\tneededResourceSet.Add(apis.GetKeyLong(&lockerResource))\n\t}\n\tfor _, lockerResource := range modifiedResources {\n\t\tmodifiedResourcesSet.Add(apis.GetKeyLong(&lockerResource))\n\t\tmodifiedResourceMap[apis.GetKeyLong(&lockerResource)] = lockerResource\n\t}\n\ttoBeDeletedKeys := strset.Difference(modifiedResourcesSet, neededResourceSet).List()\n\tfor _, resourceKey := range toBeDeletedKeys {\n\t\ttoBeDeleted = append(toBeDeleted, modifiedResourceMap[resourceKey])\n\t}\n\treturn toBeDeleted\n}\n\n// ManageError manage error sets an error status in the CR and fires an event, finally it returns the error so the operator can re-attempt\nfunc (er *EnforcingReconciler) ManageError(context context.Context, instance client.Object, issue error) (reconcile.Result, error) {\n\ter.GetRecorder().Event(instance, \"Warning\", \"ProcessingError\", issue.Error())\n\tif enforcingReconcileStatusAware, updateStatus := (instance).(v1alpha1.EnforcingReconcileStatusAware); updateStatus {\n\t\tcondition := metav1.Condition{\n\t\t\tType:               apis.ReconcileError,\n\t\t\tLastTransitionTime: metav1.Now(),\n\t\t\tMessage:            issue.Error(),\n\t\t\tObservedGeneration: instance.GetGeneration(),\n\t\t\tReason:             apis.ReconcileErrorReason,\n\t\t\tStatus:             metav1.ConditionTrue,\n\t\t}\n\t\tstatus := v1alpha1.EnforcingReconcileStatus{\n\t\t\tConditions:             apis.AddOrReplaceCondition(condition, enforcingReconcileStatusAware.GetEnforcingReconcileStatus().Conditions),\n\t\t\tLockedResourceStatuses: er.GetLockedResourceStatuses(instance),\n\t\t\tLockedPatchStatuses:    er.GetLockedPatchStatuses(instance),\n\t\t}\n\t\tenforcingReconcileStatusAware.SetEnforcingReconcileStatus(status)\n\t\terr := er.GetClient().Status().Update(context, instance)\n\t\tif err != nil {\n\t\t\tif errors.IsResourceExpired(err) {\n\t\t\t\ter.log.Info(\"unable to update status for\", \"object version\", instance.GetResourceVersion(), \"resource version expired, will trigger another reconcile cycle\", \"\")\n\t\t\t} else {\n\t\t\t\ter.log.Error(err, \"unable to update status for\", \"object\", instance)\n\t\t\t}\n\t\t\treturn reconcile.Result{}, err\n\t\t}\n\t} else {\n\t\ter.log.V(1).Info(\"object is not ReconcileStatusAware, not setting status\")\n\t}\n\treturn reconcile.Result{}, issue\n}\n\n// ManageSuccess will update the status of the CR and return a successful reconcile result\nfunc (er *EnforcingReconciler) ManageSuccess(context context.Context, instance client.Object) (reconcile.Result, error) {\n\tif enforcingReconcileStatusAware, updateStatus := (instance).(v1alpha1.EnforcingReconcileStatusAware); updateStatus {\n\t\tcondition := metav1.Condition{\n\t\t\tType:               apis.ReconcileSuccess,\n\t\t\tLastTransitionTime: metav1.Now(),\n\t\t\tObservedGeneration: instance.GetGeneration(),\n\t\t\tReason:             apis.ReconcileSuccessReason,\n\t\t\tStatus:             metav1.ConditionTrue,\n\t\t}\n\t\tstatus := v1alpha1.EnforcingReconcileStatus{\n\t\t\tConditions:             apis.AddOrReplaceCondition(condition, enforcingReconcileStatusAware.GetEnforcingReconcileStatus().Conditions),\n\t\t\tLockedResourceStatuses: er.GetLockedResourceStatuses(instance),\n\t\t\tLockedPatchStatuses:    er.GetLockedPatchStatuses(instance),\n\t\t}\n\t\tenforcingReconcileStatusAware.SetEnforcingReconcileStatus(status)\n\t\terr := er.GetClient().Status().Update(context, instance)\n\t\tif err != nil {\n\t\t\tif errors.IsResourceExpired(err) {\n\t\t\t\ter.log.Info(\"unable to update status for\", \"object version\", instance.GetResourceVersion(), \"resource version expired, will trigger another reconcile cycle\", \"\")\n\t\t\t} else {\n\t\t\t\ter.log.Error(err, \"unable to update status for\", \"object\", instance)\n\t\t\t}\n\t\t\treturn reconcile.Result{}, err\n\t\t}\n\t} else {\n\t\ter.log.V(1).Info(\"object is not ReconcileStatusAware, not setting status\")\n\t}\n\treturn reconcile.Result{}, nil\n}\n\n// GetLockedResourceStatuses returns the status for all LockedResources\nfunc (er *EnforcingReconciler) GetLockedResourceStatuses(instance client.Object) map[string]v1alpha1.Conditions {\n\tlockedResourceManager, err := er.getLockedResourceManager(instance)\n\tif err != nil {\n\t\ter.log.Error(err, \"unable to get locked resource manager for\", \"parent\", instance)\n\t\treturn map[string]v1alpha1.Conditions{}\n\t}\n\tlockedResourceReconcileStatuses := map[string]v1alpha1.Conditions{}\n\tfor _, lockedResourceReconciler := range lockedResourceManager.GetResourceReconcilers() {\n\t\tstatus := lockedResourceReconciler.GetStatus()\n\t\tif er.returnOnlyFailingStatuses {\n\t\t\tif lastCondition, ok := apis.GetLastCondition(status); ok && apis.IsErrorCondition(lastCondition) {\n\t\t\t\tlockedResourceReconcileStatuses[apis.GetKeyLong(&lockedResourceReconciler.Resource)] = status\n\t\t\t}\n\t\t} else {\n\t\t\tlockedResourceReconcileStatuses[apis.GetKeyLong(&lockedResourceReconciler.Resource)] = status\n\t\t}\n\t}\n\treturn lockedResourceReconcileStatuses\n}\n\n// GetLockedPatchStatuses returns the status for all LockedPatches\nfunc (er *EnforcingReconciler) GetLockedPatchStatuses(instance client.Object) map[string]v1alpha1.ConditionMap {\n\tlockedResourceManager, err := er.getLockedResourceManager(instance)\n\tif err != nil {\n\t\ter.log.Error(err, \"unable to get locked resource manager for\", \"parent\", instance)\n\t\treturn nil\n\t}\n\tlockedPatchReconcileStatuses := map[string]v1alpha1.ConditionMap{}\n\tfor _, lockedPatchReconciler := range lockedResourceManager.GetPatchReconcilers() {\n\t\tstatus := lockedPatchReconciler.GetStatus()\n\t\tfor key, conditions := range status {\n\t\t\tif _, ok := lockedPatchReconcileStatuses[lockedPatchReconciler.GetKey()]; !ok {\n\t\t\t\tlockedPatchReconcileStatuses[lockedPatchReconciler.GetKey()] = map[string]v1alpha1.Conditions{}\n\t\t\t}\n\t\t\tif er.returnOnlyFailingStatuses {\n\t\t\t\tif lastCondition, ok := apis.GetLastCondition(status[key]); ok && apis.IsErrorCondition(lastCondition) {\n\n\t\t\t\t\tlockedPatchReconcileStatuses[lockedPatchReconciler.GetKey()][key] = conditions\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlockedPatchReconcileStatuses[lockedPatchReconciler.GetKey()][key] = conditions\n\t\t\t}\n\t\t}\n\t}\n\treturn lockedPatchReconcileStatuses\n}\n\n// Terminate will stop the execution for the current instance. It will also optionally delete the locked resources.\nfunc (er *EnforcingReconciler) Terminate(instance client.Object, deleteResources bool) error {\n\tdefer er.removeLockedResourceManager(instance)\n\tlockedResourceManager, err := er.getLockedResourceManager(instance)\n\tif err != nil {\n\t\ter.log.Error(err, \"unable to get locked resource manager for\", \"parent\", instance)\n\t\treturn err\n\t}\n\tif lockedResourceManager.IsStarted() {\n\t\terr = lockedResourceManager.Stop(deleteResources)\n\t\tif err != nil {\n\t\t\ter.log.Error(err, \"unable to stop \", \"lockedResourceManager\", lockedResourceManager)\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/util/lockedresourcecontroller/locked-resource-manager.go",
    "content": "package lockedresourcecontroller\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\n\t\"github.com/go-logr/logr\"\n\tmultierror \"github.com/hashicorp/go-multierror\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/apis\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/discoveryclient\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller/lockedpatch\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller/lockedresource\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller/lockedresource/lockedresourceset\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/stoppablemanager\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/templates\"\n\t\"github.com/scylladb/go-set/strset\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/client-go/discovery\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/kubectl/pkg/util/openapi\"\n\t\"k8s.io/kubectl/pkg/validation\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/cache\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/event\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/manager\"\n)\n\n// LockedResourceManager is a manager designed to manage a set of LockedResourceReconciler.\n// Each reconciler can handle a LockedResource.\n// LockedResourceManager is designed to be sued within an operator to enforce a set of resources.\n// It has methods to start and stop the enforcing and to detect whether a set of resources is equal to the currently enforce set.\ntype LockedResourceManager struct {\n\tstoppableManager    *stoppablemanager.StoppableManager\n\tresources           []lockedresource.LockedResource\n\tresourceReconcilers []*LockedResourceReconciler\n\tpatches             []lockedpatch.LockedPatch\n\tpatchReconcilers    []*LockedPatchReconciler\n\tconfig              *rest.Config\n\toptions             manager.Options\n\tparent              client.Object\n\tstatusChange        chan<- event.GenericEvent\n\tclusterWatchers     bool\n\tlog                 logr.Logger\n}\n\n// NewLockedResourceManager build a new LockedResourceManager\n// config: the rest config client to be used by the controllers\n// options: the manager options\n// parent: an object to which send notification when a recocilianton cicle completes for one of the reconcilers\n// statusChange: a channel through which send the notifications\nfunc NewLockedResourceManager(config *rest.Config, options manager.Options, parent client.Object, statusChange chan<- event.GenericEvent, clusterWatchers bool) (LockedResourceManager, error) {\n\tlockedResourceManager := LockedResourceManager{\n\t\tconfig:          config,\n\t\toptions:         options,\n\t\tparent:          parent,\n\t\tstatusChange:    statusChange,\n\t\tclusterWatchers: clusterWatchers,\n\t\tlog:             ctrl.Log.WithName(\"locker-resource-manager\").WithName(apis.GetKeyShort(parent)),\n\t}\n\treturn lockedResourceManager, nil\n}\n\n// GetResources returns the currently enforced resources\nfunc (lrm *LockedResourceManager) GetResources() []lockedresource.LockedResource {\n\treturn lrm.resources\n}\n\n// GetPatches returns the currently enforced patches\nfunc (lrm *LockedResourceManager) GetPatches() []lockedpatch.LockedPatch {\n\treturn lrm.patches\n}\n\n// SetResources set the resources to be enforced. Can be called only when the LockedResourceManager is stopped.\nfunc (lrm *LockedResourceManager) SetResources(resources []lockedresource.LockedResource) error {\n\tif lrm.stoppableManager != nil && lrm.stoppableManager.IsStarted() {\n\t\treturn errors.New(\"cannot set resources while enforcing is on\")\n\t}\n\terr := lrm.validateLockedResources(resources)\n\tif err != nil {\n\t\tlrm.log.Error(err, \"unable to validate resources against running api server\")\n\t\treturn err\n\t}\n\tlrm.resources = resources\n\treturn nil\n}\n\n// SetPatches set the patches to be enforced. Can be called only when the LockedResourceManager is stopped.\nfunc (lrm *LockedResourceManager) SetPatches(patches []lockedpatch.LockedPatch) error {\n\tif lrm.stoppableManager != nil && lrm.stoppableManager.IsStarted() {\n\t\treturn errors.New(\"cannot set resources while enforcing is on\")\n\t}\n\t// verifyPatchID Uniqueness\n\tlockedPatchMap := map[string]lockedpatch.LockedPatch{}\n\tfor _, lockedPatch := range patches {\n\t\tif lockedPatch.Name == \"\" {\n\t\t\treturn errors.New(\"lockedPatch.ID must be initialized\")\n\t\t}\n\t\tif _, ok := lockedPatchMap[lockedPatch.Name]; ok {\n\t\t\treturn errors.New(\"Duplicate patch id: \" + lockedPatch.Name)\n\t\t}\n\t\tlockedPatchMap[lockedPatch.Name] = lockedPatch\n\t}\n\terr := lrm.validateLockedPatches(patches)\n\tif err != nil {\n\t\tlrm.log.Error(err, \"unable to validate patches against running api server\")\n\t\treturn err\n\t}\n\tlrm.patches = patches\n\treturn nil\n}\n\n// IsStarted returns whether the LockedResourceManager is started\nfunc (lrm *LockedResourceManager) IsStarted() bool {\n\treturn lrm.stoppableManager != nil && lrm.stoppableManager.IsStarted()\n}\n\n// Start starts the LockedResourceManager\nfunc (lrm *LockedResourceManager) Start(ctx context.Context, config *rest.Config) error {\n\tif lrm.stoppableManager != nil && lrm.stoppableManager.IsStarted() {\n\t\treturn nil\n\t}\n\n\t//diabling metrics\n\toptions := lrm.options\n\toptions.MetricsBindAddress = \"0\"\n\toptions.LeaderElection = false\n\n\tif !lrm.clusterWatchers {\n\t\tnamespaces := lrm.scanNamespaces()\n\t\tlrm.log.V(1).Info(\"starting multicache with the following \", \"namespaces\", namespaces)\n\t\toptions.NewCache = cache.MultiNamespacedCacheBuilder(namespaces)\n\t}\n\n\tstoppableManager, err := stoppablemanager.NewStoppableManager(config, options)\n\tlrm.stoppableManager = &stoppableManager\n\n\tif err != nil {\n\t\tlrm.log.Error(err, \"unable to create stoppable manager\")\n\t\treturn err\n\t}\n\n\tresourceReconcilers := []*LockedResourceReconciler{}\n\tfor _, resource := range lrm.resources {\n\t\treconciler, err := NewLockedObjectReconciler(lrm.stoppableManager.Manager, resource.Unstructured, resource.ExcludedPaths, lrm.statusChange, lrm.parent)\n\t\tif err != nil {\n\t\t\tlrm.log.Error(err, \"unable to create reconciler\", \"for locked resource\", resource)\n\t\t\treturn err\n\t\t}\n\t\tresourceReconcilers = append(resourceReconcilers, reconciler)\n\t}\n\tlrm.resourceReconcilers = resourceReconcilers\n\n\tpatchReconcilers := []*LockedPatchReconciler{}\n\tfor _, patch := range lrm.patches {\n\t\treconciler, err := NewLockedPatchReconciler(lrm.stoppableManager.Manager, patch, lrm.statusChange, lrm.parent)\n\t\tif err != nil {\n\t\t\tlrm.log.Error(err, \"unable to create reconciler\", \"for locked patch\", patch)\n\t\t\treturn err\n\t\t}\n\t\tpatchReconcilers = append(patchReconcilers, reconciler)\n\t}\n\tlrm.patchReconcilers = patchReconcilers\n\n\tlrm.stoppableManager.Start(ctx)\n\treturn nil\n}\n\n// Stop stops the LockedResourceManager.\n// deleteResource controls whether the managed resources should be deleted or left in place\n// notice that lrm will always succeed at stopping the manager, but it might fail at deleting resources\nfunc (lrm *LockedResourceManager) Stop(deleteResources bool) error {\n\tlrm.stoppableManager.Stop()\n\tif deleteResources {\n\t\terr := lrm.deleteResources(context.TODO())\n\t\tif err != nil {\n\t\t\tlrm.log.Error(err, \"unable to delete resources\")\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (lrm *LockedResourceManager) scanNamespaces() []string {\n\tnamespaceSet := strset.New()\n\tfor _, resource := range lrm.GetResources() {\n\t\tif resource.GetNamespace() != \"\" {\n\t\t\tnamespaceSet.Add(resource.GetNamespace())\n\t\t}\n\t}\n\tfor _, patch := range lrm.GetPatches() {\n\t\tif patch.TargetObjectRef.Namespace != \"\" {\n\t\t\tnamespaceSet.Add(patch.TargetObjectRef.Namespace)\n\t\t}\n\t\tfor _, sourceObj := range patch.SourceObjectRefs {\n\t\t\tif sourceObj.Namespace != \"\" {\n\t\t\t\tnamespaceSet.Add(sourceObj.Namespace)\n\t\t\t}\n\t\t}\n\t}\n\t//in case no namesopace is added it means that all of the objects are cluster scoped, then we need to add an emptu string to activate the cache.\n\tif len(lrm.GetResources())+len(lrm.GetPatches()) > 0 && len(namespaceSet.List()) == 0 {\n\t\treturn []string{\"\"}\n\t}\n\treturn namespaceSet.List()\n}\n\n// Restart restarts the manager with a different set of resources\n// if deleteResources is set, resources that were enforced are deleted.\nfunc (lrm *LockedResourceManager) Restart(ctx context.Context, resources []lockedresource.LockedResource,\n\tpatches []lockedpatch.LockedPatch, deleteResources bool, config *rest.Config) error {\n\tif lrm.IsStarted() {\n\t\terr := lrm.Stop(deleteResources)\n\t\tif err != nil {\n\t\t\tlrm.log.Error(err, \"unable to stop\", \"deleteResources\", deleteResources)\n\t\t\treturn err\n\t\t}\n\t}\n\terr := lrm.SetResources(resources)\n\tif err != nil {\n\t\tlrm.log.Error(err, \"unable to set\", \"resources\", resources)\n\t\treturn err\n\t}\n\terr = lrm.SetPatches(patches)\n\tif err != nil {\n\t\tlrm.log.Error(err, \"unable to set\", \"patches\", patches)\n\t\treturn err\n\t}\n\treturn lrm.Start(ctx, config)\n}\n\n// IsSameResources checks whether the currently enforced resources are the same as the ones passed as parameters\n// same is true is current resources are the same as the resources passed as a parameter\n// leftDifference contains the resources that are in the current resources but not in passed in the parameter\n// intersection contains resources that are both in the current resources and the parameter\n// rightDifference contains the resources that are in the parameter but not in the current resources\nfunc (lrm *LockedResourceManager) IsSameResources(resources []lockedresource.LockedResource) (same bool, leftDifference []lockedresource.LockedResource, intersection []lockedresource.LockedResource, rightDifference []lockedresource.LockedResource) {\n\tcurrentResources := lockedresourceset.New(lrm.GetResources()...)\n\tnewResources := lockedresourceset.New(resources...)\n\tleftDifference = lockedresourceset.Difference(currentResources, newResources).List()\n\tintersection = lockedresourceset.Intersection(currentResources, newResources).List()\n\trightDifference = lockedresourceset.Difference(newResources, currentResources).List()\n\tsame = currentResources.IsEqual(newResources)\n\treturn same, leftDifference, intersection, rightDifference\n}\n\n// IsSamePatches checks whether the currently enforced patches are the same as the ones passed as parameters\n// same is true is current patches are the same as the patches passed as a parameter\n// leftDifference contains the patches that are in the current patches but not in passed in the parameter\n// intersection contains patches that are both in the current patches and the parameter, the patch definition may not be the same, the definitions of those in the parameter are returned\n// rightDifference contains the patches that are in the parameter but not in the current patches\nfunc (lrm *LockedResourceManager) IsSamePatches(patches []lockedpatch.LockedPatch) (same bool, leftDifference []lockedpatch.LockedPatch, intersection []lockedpatch.LockedPatch, rightDifference []lockedpatch.LockedPatch) {\n\tcurrentPatchMap, currentPatches := lockedpatch.GetLockedPatchMap(lrm.GetPatches())\n\tnewPatchMap, newPatches := lockedpatch.GetLockedPatchMap(patches)\n\tcurrentPatchSet := strset.New(currentPatches...)\n\tnewPatchSet := strset.New(newPatches...)\n\tleftDifference = lockedpatch.GetLockedPatchesFromLockedPatcheSet(strset.Difference(currentPatchSet, newPatchSet), currentPatchMap)\n\tintersection = lockedpatch.GetLockedPatchesFromLockedPatcheSet(strset.Intersection(currentPatchSet, newPatchSet), newPatchMap)\n\trightDifference = lockedpatch.GetLockedPatchesFromLockedPatcheSet(strset.Difference(newPatchSet, currentPatchSet), newPatchMap)\n\tsame = currentPatchSet.IsEqual(newPatchSet)\n\t//we also need to check intersection to see if there are differences in the pacth definition\n\tfor _, patchID := range strset.Intersection(currentPatchSet, newPatchSet).List() {\n\t\tcurrentPatch, err := json.Marshal(currentPatchMap[patchID])\n\t\tif err != nil {\n\t\t\tlrm.log.Error(err, \"unable to Marshall\", \"currentPatch\", currentPatchMap[patchID])\n\t\t\treturn false, leftDifference, intersection, rightDifference\n\t\t}\n\t\tnewPatch, err := json.Marshal(newPatchMap[patchID])\n\t\tif err != nil {\n\t\t\tlrm.log.Error(err, \"unable to Marshall\", \"newPatch\", newPatchMap[patchID])\n\t\t\treturn false, leftDifference, intersection, rightDifference\n\t\t}\n\t\tif string(currentPatch) != string(newPatch) {\n\t\t\tsame = false\n\t\t\tbreak\n\t\t}\n\t}\n\treturn same, leftDifference, intersection, rightDifference\n}\n\nfunc (lrm *LockedResourceManager) deleteResources(context context.Context) error {\n\treconcilerBase := util.NewFromManager(lrm.stoppableManager.Manager, lrm.stoppableManager.GetEventRecorderFor(\"resource-deleter\"))\n\tfor _, resource := range lrm.GetResources() {\n\t\tgvk := resource.Unstructured.GetObjectKind().GroupVersionKind()\n\t\tgroupVersion := schema.GroupVersion{Group: gvk.Group, Version: gvk.Version}\n\t\tlrm.stoppableManager.GetScheme().AddKnownTypes(groupVersion, &resource.Unstructured)\n\t\terr := reconcilerBase.DeleteResourceIfExists(context, &resource.Unstructured)\n\t\tif err != nil {\n\t\t\tlrm.log.Error(err, \"unable to delete\", \"resource\", resource.Unstructured)\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// GetResourceReconcilers return the currently active resource reconcilers\nfunc (lrm *LockedResourceManager) GetResourceReconcilers() []*LockedResourceReconciler {\n\tif lrm.IsStarted() {\n\t\treturn lrm.resourceReconcilers\n\t}\n\treturn []*LockedResourceReconciler{}\n}\n\nfunc (lrm *LockedResourceManager) validateLockedResources(lockedResources []lockedresource.LockedResource) error {\n\tctx := context.TODO()\n\tctx = context.WithValue(ctx, \"restConfig\", lrm.config)\n\tctx = log.IntoContext(ctx, lrm.log)\n\tdiscoveryClient, err := discovery.NewDiscoveryClientForConfig(lrm.config)\n\tif err != nil {\n\t\tlrm.log.Error(err, \"unable to create discovery client\")\n\t\treturn err\n\t}\n\t//lockedResourceAPIResource.\n\t// validate the unstructured object is conformant to the openapi\n\tdoc, err := discoveryClient.OpenAPISchema()\n\tif err != nil {\n\t\tlrm.log.Error(err, \"unable to get openapi schema\")\n\t\treturn err\n\t}\n\tresources, err := openapi.NewOpenAPIData(doc)\n\tif err != nil {\n\t\tlrm.log.Error(err, \"unable to get resources from openapi doc\")\n\t\treturn err\n\t}\n\tschemaValidation := validation.NewSchemaValidation(resources)\n\tresult := &multierror.Error{}\n\tfor _, lockedResource := range lockedResources {\n\t\tdefined, err := discoveryclient.IsUnstructuredDefined(ctx, &lockedResource.Unstructured)\n\t\tif err != nil {\n\t\t\tlrm.log.Error(err, \"unable to validate\", \"unstructured\", lockedResource.Unstructured)\n\t\t\tresult = multierror.Append(result, err)\n\t\t\tcontinue\n\t\t}\n\t\tif !defined {\n\t\t\tresult = multierror.Append(result, errors.New(\"resource type:\"+lockedResource.Unstructured.GroupVersionKind().String()+\"not defined\"))\n\t\t\tcontinue\n\t\t}\n\t\terr = templates.ValidateUnstructured(ctx, &lockedResource.Unstructured, schemaValidation)\n\t\tif err != nil {\n\t\t\tlrm.log.Error(err, \"unable to validate\", \"unstructured\", lockedResource.Unstructured)\n\t\t\tresult = multierror.Append(result, err)\n\t\t\tcontinue\n\t\t}\n\t\tnamespaced, err := discoveryclient.IsUnstructuredNamespaced(ctx, &lockedResource.Unstructured)\n\t\tif err != nil {\n\t\t\tlrm.log.Error(err, \"unable to determine if namespaced\", \"unstructured\", lockedResource.Unstructured)\n\t\t\tresult = multierror.Append(result, err)\n\t\t\tcontinue\n\t\t}\n\t\tif namespaced && lockedResource.Unstructured.GetNamespace() == \"\" {\n\t\t\terr := errors.New(\"namespaced resources must specify a namespace\")\n\t\t\tlrm.log.Error(err, \"unable to validate\", \"unstructured\", lockedResource.Unstructured)\n\t\t\tresult = multierror.Append(result, err)\n\t\t\tcontinue\n\t\t}\n\t}\n\tif result.ErrorOrNil() != nil {\n\t\tlrm.log.Error(result, \"encountered errors during resources validation\")\n\t\treturn result\n\t}\n\treturn nil\n}\n\n// GetPatchReconcilers return the currently active patch reconcilers\nfunc (lrm *LockedResourceManager) GetPatchReconcilers() []*LockedPatchReconciler {\n\tif lrm.IsStarted() {\n\t\treturn lrm.patchReconcilers\n\t}\n\treturn []*LockedPatchReconciler{}\n}\n\nfunc (lrm *LockedResourceManager) validateLockedPatches(patches []lockedpatch.LockedPatch) error {\n\tctx := context.TODO()\n\tctx = context.WithValue(ctx, \"restConfig\", lrm.config)\n\tctx = log.IntoContext(ctx, lrm.log)\n\tresult := &multierror.Error{}\n\tfor _, lockedPatch := range patches {\n\t\tGVKs := []schema.GroupVersionKind{}\n\t\tfor i := range lockedPatch.SourceObjectRefs {\n\t\t\tGVKs = append(GVKs, schema.FromAPIVersionAndKind(lockedPatch.SourceObjectRefs[i].APIVersion, lockedPatch.SourceObjectRefs[i].Kind))\n\t\t}\n\t\tGVKs = append(GVKs, schema.FromAPIVersionAndKind(lockedPatch.TargetObjectRef.APIVersion, lockedPatch.TargetObjectRef.Kind))\n\t\tfor i := range GVKs {\n\t\t\tdefined, err := discoveryclient.IsGVKDefined(ctx, GVKs[i])\n\t\t\tif err != nil {\n\t\t\t\tlrm.log.Error(err, \"undefined resource in this cluster\", \"gvk\", GVKs[i])\n\t\t\t\tresult = multierror.Append(result, err)\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif !defined {\n\t\t\t\tresult = multierror.Append(result, errors.New(\"resource type:\"+GVKs[i].String()+\"not defined\"))\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// if resource.Namespaced && objref.Namespace == \"\" {\n\t\t\t// \terr := errors.New(\"namespace must be specified for namespaced resources\")\n\t\t\t// \tlrm.log.Error(err, \"unable to validate\", \"objectref\", objref)\n\t\t\t// \tresult = multierror.Append(result, err)\n\t\t\t// \tcontinue\n\t\t\t// }\n\t\t}\n\t}\n\tif result.ErrorOrNil() != nil {\n\t\tlrm.log.Error(result, \"encountered errors during patch validation\")\n\t\treturn result\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "pkg/util/lockedresourcecontroller/lockedpatch/lockedpatch.go",
    "content": "package lockedpatch\n\nimport (\n\t\"text/template\"\n\n\t\"github.com/go-logr/logr\"\n\tutilsapi \"github.com/redhat-cop/operator-utils/api/v1alpha1\"\n\tutilstemplate \"github.com/redhat-cop/operator-utils/pkg/util/templates\"\n\t\"github.com/scylladb/go-set/strset\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/client-go/rest\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n)\n\nvar log = ctrl.Log.WithName(\"lockedpatch\")\n\n// LockedPatch represents a patch that needs to be enforced.\ntype LockedPatch struct {\n\tName             string                           `json:\"name,omitempty\"`\n\tSourceObjectRefs []utilsapi.SourceObjectReference `json:\"sourceObjectRefs,omitempty\"`\n\tTargetObjectRef  utilsapi.TargetObjectReference   `json:\"targetObjectRef,omitempty\"`\n\tPatchType        types.PatchType                  `json:\"patchType,omitempty\"`\n\tPatchTemplate    string                           `json:\"patchTemplate,omitempty\"`\n\tTemplate         template.Template                `json:\"-\"`\n}\n\n// GetKey returns a not so unique key for a patch\nfunc (lp *LockedPatch) GetKey() string {\n\treturn lp.Name\n}\n\n// GetLockedPatchMap returns a map and a slice of LockedPatch, useful for set based operations. Needed for internal implementation.\nfunc GetLockedPatchMap(lockedPatches []LockedPatch) (map[string]LockedPatch, []string) {\n\tlockedPatchMap := map[string]LockedPatch{}\n\tlockedPatcheIDs := []string{}\n\tfor _, lockedPatch := range lockedPatches {\n\t\tlockedPatchMap[lockedPatch.Name] = lockedPatch\n\t\tlockedPatcheIDs = append(lockedPatcheIDs, lockedPatch.Name)\n\t}\n\treturn lockedPatchMap, lockedPatcheIDs\n}\n\nfunc GetLockedPatchesFromLockedPatcheSet(lockedPatchSet *strset.Set, lockedPatchMap map[string]LockedPatch) []LockedPatch {\n\tlockedPatches := []LockedPatch{}\n\tfor _, lockedPatchID := range lockedPatchSet.List() {\n\t\tlockedPatches = append(lockedPatches, lockedPatchMap[lockedPatchID])\n\t}\n\treturn lockedPatches\n}\n\n// GetLockedPatches returns a slice of LockedPatches from a slice of apis.Patches\nfunc GetLockedPatches(patches map[string]utilsapi.PatchSpec, config *rest.Config, logger logr.Logger) ([]LockedPatch, error) {\n\tlockedPatches := []LockedPatch{}\n\tfor key, patch := range patches {\n\t\ttemplate, err := template.New(patch.PatchTemplate).Funcs(utilstemplate.AdvancedTemplateFuncMap(config, logger)).Parse(patch.PatchTemplate)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to parse \", \"template\", patch.PatchTemplate)\n\t\t\treturn []LockedPatch{}, err\n\t\t}\n\t\tlockedPatches = append(lockedPatches, LockedPatch{\n\t\t\tSourceObjectRefs: patch.SourceObjectRefs,\n\t\t\tPatchTemplate:    patch.PatchTemplate,\n\t\t\tPatchType:        patch.PatchType,\n\t\t\tTargetObjectRef:  patch.TargetObjectRef,\n\t\t\tTemplate:         *template,\n\t\t\tName:             key,\n\t\t})\n\t}\n\treturn lockedPatches, nil\n}\n"
  },
  {
    "path": "pkg/util/lockedresourcecontroller/lockedresource/lockedresource.go",
    "content": "package lockedresource\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"text/template\"\n\n\t\"github.com/go-logr/logr\"\n\tutilsapi \"github.com/redhat-cop/operator-utils/api/v1alpha1\"\n\tutilstemplates \"github.com/redhat-cop/operator-utils/pkg/util/templates\"\n\t\"github.com/scylladb/go-set/strset\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/client-go/rest\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/yaml\"\n)\n\nvar innerlog = ctrl.Log.WithName(\"lockedresource\")\n\n// LockedResource represents a resource to be locked down by a LockedResourceReconciler within a LockedResourceManager\ntype LockedResource struct {\n\t// unstructured.Unstructured is the resource to be locked\n\tunstructured.Unstructured `json:\"usntructured,omitempty\"`\n\t// ExcludedPaths are the jsonPaths to be excluded when consider whether the resource has changed\n\tExcludedPaths []string `json:\"excludedPaths,omitempty\"`\n}\n\n// AsListOfUnstructured given a list of LockedResource, returns a list of unstructured.Unstructured\nfunc AsListOfUnstructured(lockedResources []LockedResource) []unstructured.Unstructured {\n\tunstructuredList := []unstructured.Unstructured{}\n\tfor _, lockedResource := range lockedResources {\n\t\tunstructuredList = append(unstructuredList, lockedResource.Unstructured)\n\t}\n\treturn unstructuredList\n}\n\n// GetKey returns the marshalled resource\nfunc (lr *LockedResource) GetKey() string {\n\tbb, err := lr.Unstructured.MarshalJSON()\n\tif err != nil {\n\t\tinnerlog.Error(err, \"unable to marshall\", \"unstructured\", lr.Unstructured)\n\t\tpanic(err)\n\t}\n\treturn string(bb)\n}\n\n// GetLockedResources turns an array of Resources as read from an API into an array of LockedResources, usable by the LockedResourceManager\nfunc GetLockedResources(resources []utilsapi.LockedResource) ([]LockedResource, error) {\n\tlockedResources := []LockedResource{}\n\tfor _, resource := range resources {\n\t\tbb, err := yaml.YAMLToJSON(resource.Object.Raw)\n\t\tif err != nil {\n\t\t\tinnerlog.Error(err, \"Error transforming yaml to json\", \"raw\", resource.Object.Raw)\n\t\t\treturn []LockedResource{}, err\n\t\t}\n\t\tobj := &unstructured.Unstructured{}\n\t\terr = json.Unmarshal(bb, obj)\n\t\tif err != nil {\n\t\t\tinnerlog.Error(err, \"Error unmarshalling json manifest\", \"manifest\", string(bb))\n\t\t\treturn []LockedResource{}, err\n\t\t}\n\t\tlockedResources = append(lockedResources, LockedResource{\n\t\t\tUnstructured:  *obj,\n\t\t\tExcludedPaths: resource.ExcludedPaths,\n\t\t})\n\t}\n\treturn lockedResources, nil\n}\n\nvar templates = map[string]*template.Template{}\n\n// GetLockedResourcesFromTemplates Keep backwards compatability with existing consumers\nfunc GetLockedResourcesFromTemplates(resources []utilsapi.LockedResourceTemplate, params interface{}) ([]LockedResource, error) {\n\n\treturn GetLockedResourcesFromTemplatesWithRestConfig(resources, nil, params)\n}\n\n// GetLockedResourcesFromTemplatesWithRestConfig turns an array of ResourceTemplates as read from an API into an array of LockedResources using a params to process the templates\nfunc GetLockedResourcesFromTemplatesWithRestConfig(resources []utilsapi.LockedResourceTemplate, config *rest.Config, params interface{}) ([]LockedResource, error) {\n\tlockedResources := []LockedResource{}\n\tctx := context.TODO()\n\tctx = context.WithValue(ctx, \"restConfig\", config)\n\tctx = log.IntoContext(ctx, innerlog)\n\tfor _, resource := range resources {\n\t\ttemplate, err := getTemplate(&resource, config, innerlog)\n\t\tif err != nil {\n\t\t\tinnerlog.Error(err, \"unable to retrieve template for\", \"resource\", resource)\n\t\t\treturn []LockedResource{}, nil\n\t\t}\n\t\tobjs, err := utilstemplates.ProcessTemplateArray(ctx, params, template)\n\t\tif err != nil {\n\t\t\tinnerlog.Error(err, \"unable to process template for\", \"resource\", resource, \"params\", params)\n\t\t\treturn []LockedResource{}, nil\n\t\t}\n\t\tfor _, obj := range objs {\n\t\t\tlockedResources = append(lockedResources, LockedResource{\n\t\t\t\tUnstructured:  obj,\n\t\t\t\tExcludedPaths: resource.ExcludedPaths,\n\t\t\t})\n\t\t}\n\t}\n\treturn lockedResources, nil\n}\n\nfunc getTemplate(resource *utilsapi.LockedResourceTemplate, config *rest.Config, logger logr.Logger) (*template.Template, error) {\n\ttmpl, ok := templates[resource.ObjectTemplate]\n\tvar err error\n\tif !ok {\n\t\ttmpl, err = template.New(resource.ObjectTemplate).Funcs(utilstemplates.AdvancedTemplateFuncMap(config, logger)).Parse(resource.ObjectTemplate)\n\t\tif err != nil {\n\t\t\tinnerlog.Error(err, \"unable to parse\", \"template\", resource.ObjectTemplate)\n\t\t\treturn nil, err\n\t\t}\n\t\ttemplates[resource.ObjectTemplate] = tmpl\n\t}\n\treturn tmpl, nil\n}\n\n// DefaultExcludedPaths represents paths that are exlcuded by default in all resources\nvar DefaultExcludedPaths = []string{\".metadata\", \".status\", \".spec.replicas\"}\n\n// DefaultExcludedPathsSet represents paths that are exlcuded by default in all resources\nvar DefaultExcludedPathsSet = strset.New(DefaultExcludedPaths...)\n\n// GetResources returs an arrays of apis.Resources from an arya of LockedResources, useful for mass operations on the LockedResources\nfunc GetResources(lockedResources []LockedResource) []client.Object {\n\tresources := []client.Object{}\n\tfor _, lockedResource := range lockedResources {\n\t\tresources = append(resources, &lockedResource.Unstructured)\n\t}\n\treturn resources\n}\n"
  },
  {
    "path": "pkg/util/lockedresourcecontroller/lockedresource/lockedresourceset/lockedresourceset.go",
    "content": "// Copyright (C) 2017 ScyllaDB\n// Use of this source code is governed by a ALv2-style\n// license that can be found at https://github.com/scylladb/go-set/LICENSE.\n\npackage lockedresourceset\n\nimport (\n\t\"fmt\"\n\t\"math\"\n\t\"strings\"\n\n\t\"github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller/lockedresource\"\n)\n\nvar (\n\t// helpful to not write everywhere struct{}{}\n\tnonExistent lockedresource.LockedResource\n)\n\n// Set is the main set structure that holds all the data\n// and methods used to working with the set.\ntype Set struct {\n\tm map[string]lockedresource.LockedResource\n}\n\n// New creates and initializes a new Set.\nfunc New(ts ...lockedresource.LockedResource) *Set {\n\ts := NewWithSize(len(ts))\n\ts.Add(ts...)\n\treturn s\n}\n\n// NewWithSize creates a new Set and gives make map a size hint.\nfunc NewWithSize(size int) *Set {\n\treturn &Set{make(map[string]lockedresource.LockedResource, size)}\n}\n\n// Add includes the specified items (one or more) to the Set. The underlying\n// Set s is modified. If passed nothing it silently returns.\nfunc (s *Set) Add(items ...lockedresource.LockedResource) {\n\tfor _, item := range items {\n\t\ts.m[item.GetKey()] = item\n\t}\n}\n\n// Remove deletes the specified items from the Set. The underlying Set s is\n// modified. If passed nothing it silently returns.\nfunc (s *Set) Remove(items ...lockedresource.LockedResource) {\n\tfor _, item := range items {\n\t\tdelete(s.m, item.GetKey())\n\t}\n}\n\n// Pop deletes and returns an item from the Set. The underlying Set s is\n// modified. If Set is empty, the zero value is returned.\nfunc (s *Set) Pop() lockedresource.LockedResource {\n\tfor item, value := range s.m {\n\t\tdelete(s.m, item)\n\t\treturn value\n\t}\n\treturn nonExistent\n}\n\n// Pop2 tries to delete and return an item from the Set. The underlying Set s\n// is modified. The second value is a bool that is true if the item existed in\n// the set, and false if not. If Set is empty, the zero value and false are\n// returned.\nfunc (s *Set) Pop2() (lockedresource.LockedResource, bool) {\n\tfor item, value := range s.m {\n\t\tdelete(s.m, item)\n\t\treturn value, true\n\t}\n\treturn nonExistent, false\n}\n\n// Has looks for the existence of items passed. It returns false if nothing is\n// passed. For multiple items it returns true only if all of  the items exist.\nfunc (s *Set) Has(items ...lockedresource.LockedResource) bool {\n\thas := false\n\tfor _, item := range items {\n\t\tif _, has = s.m[item.GetKey()]; !has {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn has\n}\n\n// HasAny looks for the existence of any of the items passed.\n// It returns false if nothing is passed.\n// For multiple items it returns true if any of the items exist.\nfunc (s *Set) HasAny(items ...lockedresource.LockedResource) bool {\n\thas := false\n\tfor _, item := range items {\n\t\tif _, has = s.m[item.GetKey()]; has {\n\t\t\tbreak\n\t\t}\n\t}\n\treturn has\n}\n\n// Size returns the number of items in a Set.\nfunc (s *Set) Size() int {\n\treturn len(s.m)\n}\n\n// Clear removes all items from the Set.\nfunc (s *Set) Clear() {\n\ts.m = make(map[string]lockedresource.LockedResource)\n}\n\n// IsEmpty reports whether the Set is empty.\nfunc (s *Set) IsEmpty() bool {\n\treturn s.Size() == 0\n}\n\n// IsEqual test whether s and t are the same in size and have the same items.\nfunc (s *Set) IsEqual(t *Set) bool {\n\t// return false if they are no the same size\n\tif s.Size() != t.Size() {\n\t\treturn false\n\t}\n\n\tequal := true\n\tt.Each(func(item lockedresource.LockedResource) bool {\n\t\t_, equal = s.m[item.GetKey()]\n\t\treturn equal // if false, Each() will end\n\t})\n\n\treturn equal\n}\n\n// IsSubset tests whether t is a subset of s.\nfunc (s *Set) IsSubset(t *Set) bool {\n\tif s.Size() < t.Size() {\n\t\treturn false\n\t}\n\n\tsubset := true\n\n\tt.Each(func(item lockedresource.LockedResource) bool {\n\t\t_, subset = s.m[item.GetKey()]\n\t\treturn subset\n\t})\n\n\treturn subset\n}\n\n// IsSuperset tests whether t is a superset of s.\nfunc (s *Set) IsSuperset(t *Set) bool {\n\treturn t.IsSubset(s)\n}\n\n// Each traverses the items in the Set, calling the provided function for each\n// Set member. Traversal will continue until all items in the Set have been\n// visited, or if the closure returns false.\nfunc (s *Set) Each(f func(item lockedresource.LockedResource) bool) {\n\tfor _, value := range s.m {\n\t\tif !f(value) {\n\t\t\tbreak\n\t\t}\n\t}\n}\n\n// Copy returns a new Set with a copy of s.\nfunc (s *Set) Copy() *Set {\n\tu := NewWithSize(s.Size())\n\tfor item, value := range s.m {\n\t\tu.m[item] = value\n\t}\n\treturn u\n}\n\n// String returns a string representation of s\nfunc (s *Set) String() string {\n\tv := make([]string, 0, s.Size())\n\tfor item := range s.m {\n\t\tv = append(v, fmt.Sprintf(\"%v\", item))\n\t}\n\treturn fmt.Sprintf(\"[%s]\", strings.Join(v, \", \"))\n}\n\n// List returns a slice of all items. There is also StringSlice() and\n// IntSlice() methods for returning slices of type string or int.\nfunc (s *Set) List() []lockedresource.LockedResource {\n\tv := make([]lockedresource.LockedResource, 0, s.Size())\n\tfor _, value := range s.m {\n\t\tv = append(v, value)\n\t}\n\treturn v\n}\n\n// Merge is like Union, however it modifies the current Set it's applied on\n// with the given t Set.\nfunc (s *Set) Merge(t *Set) {\n\tfor item, value := range t.m {\n\t\ts.m[item] = value\n\t}\n}\n\n// Separate removes the Set items containing in t from Set s. Please aware that\n// it's not the opposite of Merge.\nfunc (s *Set) Separate(t *Set) {\n\tfor item := range t.m {\n\t\tdelete(s.m, item)\n\t}\n}\n\n// Union is the merger of multiple sets. It returns a new set with all the\n// elements present in all the sets that are passed.\nfunc Union(sets ...*Set) *Set {\n\tmaxPos := -1\n\tmaxSize := 0\n\tfor i, set := range sets {\n\t\tif l := set.Size(); l > maxSize {\n\t\t\tmaxSize = l\n\t\t\tmaxPos = i\n\t\t}\n\t}\n\tif maxSize == 0 {\n\t\treturn New()\n\t}\n\n\tu := sets[maxPos].Copy()\n\tfor i, set := range sets {\n\t\tif i == maxPos {\n\t\t\tcontinue\n\t\t}\n\t\tfor item, value := range set.m {\n\t\t\tu.m[item] = value\n\t\t}\n\t}\n\treturn u\n}\n\n// Difference returns a new set which contains items which are in in the first\n// set but not in the others.\nfunc Difference(set1 *Set, sets ...*Set) *Set {\n\ts := set1.Copy()\n\tfor _, set := range sets {\n\t\ts.Separate(set)\n\t}\n\treturn s\n}\n\n// Intersection returns a new set which contains items that only exist in all\n// given sets.\nfunc Intersection(sets ...*Set) *Set {\n\tminPos := -1\n\tminSize := math.MaxInt64\n\tfor i, set := range sets {\n\t\tif l := set.Size(); l < minSize {\n\t\t\tminSize = l\n\t\t\tminPos = i\n\t\t}\n\t}\n\tif minSize == math.MaxInt64 || minSize == 0 {\n\t\treturn New()\n\t}\n\n\tt := sets[minPos].Copy()\n\tfor i, set := range sets {\n\t\tif i == minPos {\n\t\t\tcontinue\n\t\t}\n\t\tfor item := range t.m {\n\t\t\tif _, has := set.m[item]; !has {\n\t\t\t\tdelete(t.m, item)\n\t\t\t}\n\t\t}\n\t}\n\treturn t\n}\n\n// SymmetricDifference returns a new set which s is the difference of items\n// which are in one of either, but not in both.\nfunc SymmetricDifference(s *Set, t *Set) *Set {\n\tu := Difference(s, t)\n\tv := Difference(t, s)\n\treturn Union(u, v)\n}\n"
  },
  {
    "path": "pkg/util/lockedresourcecontroller/lockedresource/lockedresourceset/lockedresourceset_bench_test.go",
    "content": "// Copyright (C) 2017 ScyllaDB\n// Use of this source code is governed by a ALv2-style\n// license that can be found at https://github.com/scylladb/go-set/LICENSE.\n\npackage lockedresourceset\n\n// import (\n// \t\"testing\"\n\n// \t\"github.com/fatih/set\"\n// \t\"github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller/lockedresource\"\n// )\n\n// func BenchmarkTypeSafeSetHasNonExisting(b *testing.B) {\n// \tb.StopTimer()\n// \tvar e1 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n// \tb.StartTimer()\n// \ts := New()\n// \tfor i := 0; i < b.N; i++ {\n// \t\ts.Has(e1)\n// \t}\n// }\n\n// func BenchmarkInterfaceSetHasNonExisting(b *testing.B) {\n// \tb.StopTimer()\n// \tvar e1 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n// \tb.StartTimer()\n// \ts := set.New(set.NonThreadSafe)\n// \tfor i := 0; i < b.N; i++ {\n// \t\ts.Has(e1)\n// \t}\n// }\n\n// func BenchmarkTypeSafeSetHasExisting(b *testing.B) {\n// \tb.StopTimer()\n// \tvar e1 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n// \tb.StartTimer()\n// \ts := New()\n// \ts.Add(e1)\n// \tfor i := 0; i < b.N; i++ {\n// \t\ts.Has(e1)\n// \t}\n// }\n\n// func BenchmarkInterfaceSetHasExisting(b *testing.B) {\n// \tb.StopTimer()\n// \tvar e1 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n// \tb.StartTimer()\n// \ts := set.New(set.NonThreadSafe)\n// \ts.Add(e1)\n// \tfor i := 0; i < b.N; i++ {\n// \t\ts.Has(e1)\n// \t}\n// }\n\n// func BenchmarkTypeSafeSetHasExistingMany(b *testing.B) {\n// \ts := New()\n// \tb.StopTimer()\n// \tvar e1 lockedresource.LockedResource\n// \tfor i := 0; i < 10000; i++ {\n// \t\te := createRandomObject(e1)\n// \t\tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\t\ts.Add(v)\n// \t\t\tif i == 5000 {\n// \t\t\t\te1 = v\n// \t\t\t}\n// \t\t}\n// \t}\n// \tb.StartTimer()\n// \tfor i := 0; i < b.N; i++ {\n// \t\ts.Has(e1)\n// \t}\n// }\n\n// func BenchmarkInterfaceSetHasExistingMany(b *testing.B) {\n// \ts := set.New(set.NonThreadSafe)\n// \tb.StopTimer()\n// \tvar e1 lockedresource.LockedResource\n// \tfor i := 0; i < 10000; i++ {\n// \t\te := createRandomObject(e1)\n// \t\tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\t\ts.Add(v)\n// \t\t\tif i == 5000 {\n// \t\t\t\te1 = v\n// \t\t\t}\n// \t\t}\n// \t}\n// \tb.StartTimer()\n// \tfor i := 0; i < b.N; i++ {\n// \t\ts.Has(e1)\n// \t}\n// }\n\n// func BenchmarkTypeSafeSetAdd(b *testing.B) {\n// \tb.StopTimer()\n// \tvar e lockedresource.LockedResource\n// \ts := New()\n// \tobjs := make([]lockedresource.LockedResource, 0, b.N)\n// \tfor i := 0; i < b.N; i++ {\n// \t\te := createRandomObject(e)\n// \t\tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\t\tobjs = append(objs, v)\n// \t\t}\n// \t}\n// \tb.StartTimer()\n// \tfor i := 0; i < b.N; i++ {\n// \t\ts.Add(objs[i])\n// \t}\n// }\n\n// func BenchmarkInterfaceSetAdd(b *testing.B) {\n// \tb.StopTimer()\n// \tvar e lockedresource.LockedResource\n// \ts := set.New(set.NonThreadSafe)\n// \tobjs := make([]lockedresource.LockedResource, 0, b.N)\n// \tfor i := 0; i < b.N; i++ {\n// \t\te := createRandomObject(e)\n// \t\tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\t\tobjs = append(objs, v)\n// \t\t}\n// \t}\n// \tb.StartTimer()\n// \tfor i := 0; i < b.N; i++ {\n// \t\ts.Add(objs[i])\n// \t}\n// }\n"
  },
  {
    "path": "pkg/util/lockedresourcecontroller/lockedresource/lockedresourceset/lockedresourceset_test.go",
    "content": "// Copyright (C) 2017 ScyllaDB\n// Use of this source code is governed by a ALv2-style\n// license that can be found at https://github.com/scylladb/go-set/LICENSE.\n\npackage lockedresourceset\n\n// import (\n// \t\"fmt\"\n// \t\"math/rand\"\n// \t\"reflect\"\n// \t\"strings\"\n// \t\"testing\"\n// \t\"testing/quick\"\n// \t\"time\"\n\n// \t\"github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller/lockedresource\"\n// )\n\n// func TestAdd(t *testing.T) {\n// \tvar e1, e2 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n// \te = createRandomObject(e2)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te2 = v\n// \t}\n\n// \ts := New()\n// \ts.Add(e1)\n// \ts.Add(e2)\n\n// \tif len(s.m) != 2 {\n// \t\tt.Errorf(\"expected 2 entries, got %d\", len(s.m))\n// \t}\n// }\n\n// func TestRemove(t *testing.T) {\n// \tvar e1, e2 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n// \te = createRandomObject(e2)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te2 = v\n// \t}\n\n// \ts := New()\n// \ts.Add(e1)\n// \ts.Add(e2)\n\n// \ts.Remove(e1)\n\n// \tif len(s.m) != 1 {\n// \t\tt.Errorf(\"expected 1 entries, got %d\", len(s.m))\n// \t}\n\n// \tif _, ok := s.m[e2.GetKey()]; !ok {\n// \t\tt.Errorf(\"wrong entry %v removed, expected %v\", e1, e2)\n// \t}\n// }\n\n// func TestPop(t *testing.T) {\n// \tvar e1, e2 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n// \te = createRandomObject(e2)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te2 = v\n// \t}\n\n// \ts := New()\n// \tpopped := s.Pop()\n// \tif !reflect.DeepEqual(popped, nonExistent) {\n// \t\tt.Errorf(\"default non existent sentinel not returned, instead got %v\", popped)\n// \t}\n\n// \ts.Add(e1)\n// \ts.Add(e2)\n\n// \ts.Pop()\n\n// \tif len(s.m) != 1 {\n// \t\tt.Errorf(\"expected 1 entries, got %d\", len(s.m))\n// \t}\n// }\n\n// func TestPop2(t *testing.T) {\n// \tvar e1, e2 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n// \te = createRandomObject(e2)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te2 = v\n// \t}\n\n// \ts := New()\n// \tpopped, found := s.Pop2()\n// \tif !reflect.DeepEqual(popped, nonExistent) {\n// \t\tt.Errorf(\"default non existent sentinel not returned, instead got %v\", popped)\n// \t}\n// \tif found {\n// \t\tt.Errorf(\"set is empty, no element should have been found\")\n// \t}\n\n// \ts.Add(e1)\n// \ts.Add(e2)\n\n// \t_, found = s.Pop2()\n\n// \tif len(s.m) != 1 {\n// \t\tt.Errorf(\"expected 1 entries, got %d\", len(s.m))\n// \t}\n// \tif !found {\n// \t\tt.Errorf(\"expected to find an entry\")\n// \t}\n// }\n\n// func TestHas(t *testing.T) {\n// \tvar e1, e2, e3 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n// \te = createRandomObject(e2)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te2 = v\n// \t}\n// \te = createRandomObject(e2)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te3 = v\n// \t}\n\n// \ts := New()\n// \tif s.Has(e1) {\n// \t\tt.Errorf(\"expected a new set to not contain %v\", e1)\n// \t}\n\n// \ts.Add(e1)\n// \ts.Add(e2)\n\n// \tif !s.Has(e1) {\n// \t\tt.Errorf(\"expected the set to contain %v\", e1)\n// \t}\n// \tif !s.Has(e2) {\n// \t\tt.Errorf(\"expected the set to contain %v\", e2)\n// \t}\n// \tif s.Has(e3) {\n// \t\tt.Errorf(\"did not expect the set to contain %v\", e3)\n// \t}\n// }\n\n// func TestHasAny(t *testing.T) {\n// \tvar e1, e2, e3 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n// \te = createRandomObject(e2)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te2 = v\n// \t}\n// \te = createRandomObject(e2)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te3 = v\n// \t}\n\n// \ts := New()\n// \tif s.Has(e1) {\n// \t\tt.Errorf(\"expected a new set to not contain %v\", e1)\n// \t}\n\n// \ts.Add(e1)\n// \ts.Add(e2)\n\n// \tif !s.HasAny(e1) {\n// \t\tt.Errorf(\"expected the set to contain %v\", e1)\n// \t}\n// \tif !s.HasAny(e2) {\n// \t\tt.Errorf(\"expected the set to contain %v\", e2)\n// \t}\n// \tif s.HasAny(e3) {\n// \t\tt.Errorf(\"did not expect the set to contain %v\", e3)\n// \t}\n// \tif !s.HasAny(e1, e3) {\n// \t\tt.Errorf(\"expected the set to contain %v\", e1)\n// \t}\n// }\n\n// func TestSize(t *testing.T) {\n// \tvar e1, e2 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n// \te = createRandomObject(e2)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te2 = v\n// \t}\n\n// \ts := New()\n// \ts.Add(e1)\n// \ts.Add(e2)\n\n// \tif s.Size() != 2 {\n// \t\tt.Errorf(\"expected the set size to be 2 but it was %d\", s.Size())\n// \t}\n// }\n\n// func TestClear(t *testing.T) {\n// \tvar e1, e2 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n// \te = createRandomObject(e2)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te2 = v\n// \t}\n\n// \ts := New()\n// \ts.Add(e1)\n// \ts.Add(e2)\n\n// \ts.Clear()\n\n// \tif s.Size() != 0 {\n// \t\tt.Errorf(\"expected the cleared set size to be 0 but it was %d\", s.Size())\n// \t}\n// }\n\n// func TestIsEmpty(t *testing.T) {\n// \tvar e1, e2 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n// \te = createRandomObject(e2)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te2 = v\n// \t}\n\n// \ts := New()\n// \tif !s.IsEmpty() {\n// \t\tt.Errorf(\"expected new set to be empty but it had %d elements\", s.Size())\n// \t}\n\n// \ts.Add(e1)\n// \ts.Add(e2)\n\n// \tif s.IsEmpty() {\n// \t\tt.Error(\"expected a set with added items to not be empty\")\n// \t}\n// }\n\n// func TestIsEqual(t *testing.T) {\n// \tvar e1, e2 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n// \te = createRandomObject(e2)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te2 = v\n// \t}\n\n// \ts1 := New()\n// \ts2 := New()\n// \tif !s1.IsEqual(s2) {\n// \t\tt.Error(\"expected 2 new sets to be equal\")\n// \t}\n\n// \ts1.Add(e1)\n// \ts1.Add(e2)\n// \tif s1.IsEqual(s2) {\n// \t\tt.Errorf(\"expected 2 different sets to be equal, %v, %v\", s1, s2)\n// \t}\n// \ts2.Add(e1)\n// \ts2.Add(e2)\n\n// \tif !s1.IsEqual(s2) {\n// \t\tt.Error(\"expected 2 sets with the same items added to be equal\")\n// \t}\n// }\n\n// func TestIsSubset(t *testing.T) {\n// \tvar e1, e2, e3 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n// \te = createRandomObject(e2)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te2 = v\n// \t}\n// \te = createRandomObject(e2)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te3 = v\n// \t}\n\n// \ts1 := New()\n// \ts2 := New()\n\n// \ts1.Add(e1)\n// \ts1.Add(e2)\n// \ts2.Add(e1)\n\n// \tif !s1.IsSubset(s2) {\n// \t\tt.Errorf(\"expected %v to be a subset of %v\", s2, s1)\n// \t}\n\n// \ts2.Add(e2)\n// \ts2.Add(e3)\n// \tif s1.IsSubset(s2) {\n// \t\tt.Errorf(\"expected %v not to be a subset of %v\", s2, s1)\n// \t}\n// }\n\n// func TestIsSuperset(t *testing.T) {\n// \tvar e1, e2 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n// \te = createRandomObject(e2)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te2 = v\n// \t}\n\n// \ts1 := New()\n// \ts2 := New()\n\n// \ts1.Add(e1)\n// \ts1.Add(e2)\n// \ts2.Add(e1)\n\n// \tif !s2.IsSuperset(s1) {\n// \t\tt.Errorf(\"expected %v to be a super set of %v\", s1, s2)\n// \t}\n// }\n\n// func TestCopy(t *testing.T) {\n// \tvar e1, e2 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n// \te = createRandomObject(e2)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te2 = v\n// \t}\n\n// \ts1 := New()\n\n// \ts1.Add(e1)\n// \ts1.Add(e2)\n\n// \ts2 := s1.Copy()\n\n// \tif !s2.IsEqual(s1) {\n// \t\tt.Errorf(\"expected %v to be equal to %v after copy\", s1, s2)\n// \t}\n// }\n\n// func TestString(t *testing.T) {\n// \tvar e1 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n\n// \ts1 := New()\n\n// \ts1.Add(e1)\n\n// \ts := s1.String()\n\n// \tif s == \"\" {\n// \t\tt.Errorf(\"expected string representation %v to exist\", s)\n// \t}\n\n// \tif !strings.HasPrefix(s, \"[\") && !strings.HasSuffix(s, \"]\") {\n// \t\tt.Errorf(\"expected string representation %v to start with '[' and end with ']'\", s)\n// \t}\n// }\n\n// func TestMerge(t *testing.T) {\n// \tvar e1, e2 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n// \te = createRandomObject(e2)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te2 = v\n// \t}\n\n// \ts1 := New()\n// \ts2 := New()\n\n// \ts1.Add(e1)\n// \ts2.Add(e2)\n\n// \ts1.Merge(s2)\n\n// \tif s1.Size() != 2 {\n// \t\tt.Errorf(\"expected merged set %v have size 2 but it has %d\", s1, s1.Size())\n// \t}\n// }\n\n// func TestList(t *testing.T) {\n// \tvar e1, e2 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n// \te = createRandomObject(e2)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te2 = v\n// \t}\n\n// \ts := New()\n\n// \ts.Add(e1)\n// \ts.Add(e2)\n\n// \tl := s.List()\n\n// \tif len(l) != s.Size() {\n// \t\tt.Errorf(\"expected a set of size %d to give a list of size 2 but it has %d\", s.Size(), len(l))\n// \t}\n\n// \tfor _, e := range l {\n// \t\tif !s.Has(e) {\n// \t\t\tt.Errorf(\"listed entry %v not available in the set %s\", e, s)\n// \t\t}\n// \t}\n// }\n\n// func TestSeparate(t *testing.T) {\n// \tvar e1, e2 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n// \te = createRandomObject(e2)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te2 = v\n// \t}\n\n// \ts1 := New()\n// \ts2 := New()\n\n// \ts1.Add(e1)\n// \ts1.Add(e2)\n\n// \ts2.Add(e2)\n\n// \ts1.Separate(s2)\n\n// \tif s1.Size() != 1 {\n// \t\tt.Errorf(\"expected separated set %v have size 1 but it has %d\", s1, s1.Size())\n// \t}\n\n// \tif s1.Has(e2) {\n// \t\tt.Errorf(\"separated set %v still contains %v\", s1, e2)\n// \t}\n// }\n\n// func TestEach(t *testing.T) {\n// \tvar e1, e2 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n// \te = createRandomObject(e2)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te2 = v\n// \t}\n\n// \ts1 := New()\n// \ts1.Add(e1)\n// \ts1.Add(e2)\n\n// \tfound := make(map[string]bool)\n\n// \ts1.Each(func(item lockedresource.LockedResource) bool {\n// \t\tfound[item.GetKey()] = true\n// \t\treturn true\n// \t})\n\n// \tif len(found) != 2 {\n// \t\tt.Errorf(\"not all items traversed only %v\", found)\n// \t}\n\n// \tfound = make(map[string]bool)\n// \tcount := 0\n// \ts1.Each(func(item lockedresource.LockedResource) bool {\n// \t\tfound[item.GetKey()] = true\n// \t\tcount++\n// \t\tif count > 0 {\n// \t\t\treturn false\n// \t\t}\n// \t\treturn true\n// \t})\n\n// \tif len(found) != 1 {\n// \t\tt.Errorf(\"more than expected 1 items traversed %v\", found)\n// \t}\n// }\n\n// func TestIntersection(t *testing.T) {\n// \tvar e1, e2, e3 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n// \te = createRandomObject(e2)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te2 = v\n// \t}\n// \te = createRandomObject(e3)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te3 = v\n// \t}\n\n// \ts1 := New()\n// \ts1.Add(e1)\n// \ts1.Add(e2)\n\n// \ts2 := New()\n// \ts2.Add(e2)\n// \ts2.Add(e3)\n\n// \ts3 := Intersection(s1, s2)\n\n// \tif s3.Size() != 1 || !s3.Has(e2) {\n// \t\tt.Errorf(\"expected the intersection to only contain '%v' but it is %v\", e2, s3.List())\n// \t}\n// }\n\n// func TestUnion(t *testing.T) {\n// \tvar e1, e2, e3 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n// \te = createRandomObject(e2)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te2 = v\n// \t}\n// \te = createRandomObject(e3)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te3 = v\n// \t}\n\n// \ts1 := New()\n// \ts1.Add(e1)\n// \ts1.Add(e2)\n\n// \ts2 := New()\n// \ts2.Add(e2)\n// \ts2.Add(e3)\n\n// \ts3 := Union(s1, s2)\n\n// \tif s3.Size() != 3 || !(s3.Has(e1) && s3.Has(e2) && s3.Has(e3)) {\n// \t\tt.Errorf(\"expected the intersection to only contain %v but it is %v\", e2, s3.List())\n// \t}\n// }\n\n// func TestDifference(t *testing.T) {\n// \tvar e1, e2, e3 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n// \te = createRandomObject(e2)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te2 = v\n// \t}\n// \te = createRandomObject(e3)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te3 = v\n// \t}\n\n// \ts1 := New()\n// \ts1.Add(e1)\n// \ts1.Add(e2)\n\n// \ts2 := New()\n// \ts2.Add(e2)\n// \ts2.Add(e3)\n\n// \ts3 := Difference(s1, s2)\n\n// \tif s3.Size() != 1 || !s3.Has(e1) {\n// \t\tt.Errorf(\"expected the intersection to only contain %v but it is %v\", e2, s3.List())\n// \t}\n// }\n\n// func TestSymmetricDifference(t *testing.T) {\n// \tvar e1, e2, e3 lockedresource.LockedResource\n// \te := createRandomObject(e1)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te1 = v\n// \t}\n// \te = createRandomObject(e2)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te2 = v\n// \t}\n// \te = createRandomObject(e3)\n// \tif v, ok := e.(lockedresource.LockedResource); ok {\n// \t\te3 = v\n// \t}\n\n// \ts1 := New()\n// \ts1.Add(e1)\n// \ts1.Add(e2)\n\n// \ts2 := New()\n// \ts2.Add(e2)\n// \ts2.Add(e3)\n\n// \ts3 := SymmetricDifference(s1, s2)\n\n// \tif s3.Size() != 2 || !(s3.Has(e1) && s3.Has(e3)) {\n// \t\tt.Errorf(\"expected the intersection to only contain %v but it is %v\", e2, s3.List())\n// \t}\n// }\n\n// func createRandomObject(i interface{}) interface{} {\n// \tv, ok := quick.Value(reflect.TypeOf(i), rand.New(rand.NewSource(time.Now().UnixNano())))\n// \tif !ok {\n// \t\tpanic(fmt.Sprintf(\"unsupported type %v\", i))\n// \t}\n// \treturn v.Interface()\n// }\n"
  },
  {
    "path": "pkg/util/lockedresourcecontroller/lockedresource/patch.go",
    "content": "package lockedresource\n\nimport (\n\t\"encoding/json\"\n\t\"strings\"\n\n\tjsonpatch \"github.com/evanphx/json-patch\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n)\n\nfunc FilterOutPaths(obj *unstructured.Unstructured, jsonPaths []string) (*unstructured.Unstructured, error) {\n\tdoc, err := obj.MarshalJSON()\n\tif err != nil {\n\t\tinnerlog.Error(err, \"unable to marshall\", \"unstructured\", obj)\n\t\treturn &unstructured.Unstructured{}, err\n\t}\n\n\tpatches, err := createPatchesFromJSONPaths(jsonPaths)\n\tif err != nil {\n\t\tinnerlog.Error(err, \"unable to create patches from\", \"jsonPaths\", jsonPaths)\n\t\treturn &unstructured.Unstructured{}, err\n\t}\n\tfor _, patch := range patches {\n\t\tdecodedPatch, err := jsonpatch.DecodePatch(patch)\n\t\tif err != nil {\n\t\t\tinnerlog.Error(err, \"unable to decode\", \"patch\", string(patch))\n\t\t\treturn &unstructured.Unstructured{}, err\n\t\t}\n\t\tdoc1, err := decodedPatch.Apply(doc)\n\t\tif err != nil {\n\t\t\tif strings.Contains(err.Error(), \"Unable to remove nonexistent key\") || strings.Contains(err.Error(), \"remove operation does not apply: doc is missing path\") {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tinnerlog.Error(err, \"unable to apply\", \"patch\", patch, \"to json\", string(doc))\n\t\t\treturn &unstructured.Unstructured{}, err\n\t\t}\n\t\tdoc = doc1\n\t}\n\n\tvar result = &unstructured.Unstructured{}\n\n\terr = result.UnmarshalJSON(doc)\n\n\tif err != nil {\n\t\tinnerlog.Error(err, \"unable to unMarshall\", \"json\", doc)\n\t\treturn &unstructured.Unstructured{}, err\n\t}\n\n\treturn result, nil\n}\n\n// Patch represents a patch operation\ntype Patch struct {\n\tOperation string `json:\"op\"`\n\tPath      string `json:\"path\"`\n}\n\nfunc createPatchesFromJSONPaths(jsonPaths []string) ([][]byte, error) {\n\tresult := [][]byte{}\n\tfor _, jsonPath := range jsonPaths {\n\t\tpatch := []Patch{\n\t\t\t{\n\t\t\t\tOperation: \"remove\",\n\t\t\t\tPath:      getMergePathFromJSONPath(jsonPath),\n\t\t\t},\n\t\t}\n\t\tmpatch, err := json.Marshal(patch)\n\t\tif err != nil {\n\t\t\tinnerlog.Error(err, \"unable to marshal\", \"patch\", patch)\n\t\t\treturn [][]byte{}, err\n\t\t}\n\t\tresult = append(result, mpatch)\n\t}\n\treturn result, nil\n}\n\nfunc getMergePathFromJSONPath(jsonPath string) string {\n\t//remove \"$\" if present\n\tjsonPath = strings.TrimPrefix(jsonPath, \"$\")\n\t// convert \"[\" and \"]\" to \".\"\n\tif strings.HasSuffix(jsonPath, \"]\") {\n\t\tjsonPath = jsonPath[:len(jsonPath)-2]\n\t}\n\tjsonPath = strings.ReplaceAll(jsonPath, \"[\", \".\")\n\tjsonPath = strings.ReplaceAll(jsonPath, \"]\", \".\")\n\t// convert \".\" to \"/\"\n\tjsonPath = strings.ReplaceAll(jsonPath, \".\", \"/\")\n\treturn jsonPath\n}\n"
  },
  {
    "path": "pkg/util/lockedresourcecontroller/patch-reconciler.go",
    "content": "package lockedresourcecontroller\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"reflect\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/go-logr/logr\"\n\tutilsapi \"github.com/redhat-cop/operator-utils/api/v1alpha1\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/apis\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller/lockedpatch\"\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/runtime/schema\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\t\"k8s.io/client-go/discovery\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/util/jsonpath\"\n\t\"k8s.io/client-go/util/workqueue\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller\"\n\t\"sigs.k8s.io/controller-runtime/pkg/event\"\n\t\"sigs.k8s.io/controller-runtime/pkg/handler\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/manager\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\t\"sigs.k8s.io/controller-runtime/pkg/source\"\n\t\"sigs.k8s.io/yaml\"\n)\n\n// LockedPatchReconciler is a reconciler that can enforce a LockedPatch\ntype LockedPatchReconciler struct {\n\tutil.ReconcilerBase\n\tpatch        lockedpatch.LockedPatch\n\tstatus       map[string][]metav1.Condition\n\tstatusChange chan<- event.GenericEvent\n\tparentObject client.Object\n\tstatusLock   sync.Mutex\n\tlog          logr.Logger\n}\n\n// NewLockedPatchReconciler returns a new reconcile.Reconciler\nfunc NewLockedPatchReconciler(mgr manager.Manager, patch lockedpatch.LockedPatch, statusChange chan<- event.GenericEvent, parentObject client.Object) (*LockedPatchReconciler, error) {\n\n\t// TODO create the object is it does not exists\n\tcontrollername := \"patch-reconciler\"\n\n\treconciler := &LockedPatchReconciler{\n\t\tlog:            ctrl.Log.WithName(controllername).WithName(apis.GetKeyShort(parentObject)).WithName(patch.GetKey()),\n\t\tReconcilerBase: util.NewFromManager(mgr, mgr.GetEventRecorderFor(controllername+\"_\"+patch.GetKey())),\n\t\tpatch:          patch,\n\t\tstatusChange:   statusChange,\n\t\tparentObject:   parentObject,\n\t\tstatusLock:     sync.Mutex{},\n\t\tstatus: map[string][]metav1.Condition{\n\t\t\t\"reconciler\": []metav1.Condition([]metav1.Condition{{\n\t\t\t\tType:               \"Initializing\",\n\t\t\t\tLastTransitionTime: metav1.Now(),\n\t\t\t\tStatus:             metav1.ConditionTrue,\n\t\t\t\tObservedGeneration: 0,\n\t\t\t\tReason:             \"ReconcilerManagerRestarting\",\n\t\t\t}}),\n\t\t},\n\t}\n\n\tcontroller, err := controller.New(controllername+\"_\"+patch.GetKey(), mgr, controller.Options{Reconciler: reconciler})\n\tif err != nil {\n\t\treturn &LockedPatchReconciler{}, err\n\t}\n\n\t//create watcher for target\n\tobj := targetObjectRefToRuntimeType(&patch.TargetObjectRef)\n\tmgr.GetScheme().AddKnownTypes(schema.FromAPIVersionAndKind(patch.TargetObjectRef.APIVersion, patch.TargetObjectRef.Kind).GroupVersion(), obj)\n\n\terr = controller.Watch(source.Kind(mgr.GetCache(), obj), &handler.EnqueueRequestForObject{}, &targetReferenceModifiedPredicate{\n\t\tTargetObjectReference: patch.TargetObjectRef,\n\t\tlog:                   reconciler.log.WithName(\"target-watcher\"),\n\t\trestConfig:            mgr.GetConfig(),\n\t})\n\tif err != nil {\n\t\treturn &LockedPatchReconciler{}, err\n\t}\n\tdiscoveryClient, err := discovery.NewDiscoveryClientForConfig(mgr.GetConfig())\n\tif err != nil {\n\t\treturn &LockedPatchReconciler{}, err\n\t}\n\tfor _, sourceRef := range patch.SourceObjectRefs {\n\t\tobj := sourceObjectRefToRuntimeType(&sourceRef)\n\t\tmgr.GetScheme().AddKnownTypes(schema.FromAPIVersionAndKind(sourceRef.APIVersion, sourceRef.Kind).GroupVersion(), obj)\n\t\terr = controller.Watch(source.Kind(mgr.GetCache(), obj), &enqueueRequestForPatch{\n\t\t\tsource:          &sourceRef,\n\t\t\ttarget:          &patch.TargetObjectRef,\n\t\t\tdiscoveryClient: discoveryClient,\n\t\t\trestConfig:      mgr.GetConfig(),\n\t\t\tlog:             reconciler.log.WithName(sourceRef.APIVersion + \"/\" + sourceRef.Kind + \"/\" + sourceRef.Namespace + \"/\" + sourceRef.Name).WithName(\"source-event-handler\"),\n\t\t}, &sourceReferenceModifiedPredicate{\n\t\t\tlog:        reconciler.log.WithName(sourceRef.APIVersion + \"/\" + sourceRef.Kind + \"/\" + sourceRef.Namespace + \"/\" + sourceRef.Name).WithName(\"source-event-filter\"),\n\t\t\tsource:     &sourceRef,\n\t\t\ttarget:     &patch.TargetObjectRef,\n\t\t\trestConfig: mgr.GetConfig(),\n\t\t})\n\t\tif err != nil {\n\t\t\treturn &LockedPatchReconciler{}, err\n\t\t}\n\t}\n\n\treturn reconciler, nil\n}\n\nfunc sourceObjectRefToRuntimeType(objref *utilsapi.SourceObjectReference) client.Object {\n\tobj := &unstructured.Unstructured{}\n\tobj.SetKind(objref.Kind)\n\tobj.SetAPIVersion(objref.APIVersion)\n\treturn obj\n}\n\nfunc targetObjectRefToRuntimeType(objref *utilsapi.TargetObjectReference) client.Object {\n\tobj := &unstructured.Unstructured{}\n\tobj.SetKind(objref.Kind)\n\tobj.SetAPIVersion(objref.APIVersion)\n\treturn obj\n}\n\ntype enqueueRequestForPatch struct {\n\tsource          *utilsapi.SourceObjectReference\n\ttarget          *utilsapi.TargetObjectReference\n\tdiscoveryClient *discovery.DiscoveryClient\n\trestConfig      *rest.Config\n\tlog             logr.Logger\n}\n\nfunc (e *enqueueRequestForPatch) Create(ctx context.Context, evt event.CreateEvent, q workqueue.RateLimitingInterface) {\n\t//to see if this event is relevant and we have to do the following:\n\t// 1. see if the target is single or multiple\n\t// 2. if single just see if it matches, the pass the event.\n\t// 3. if multiple see which macth and then pass the event\n\te.log.V(1).Info(\"enqueue create\", \"for\", evt.Object)\n\tctx = context.WithValue(ctx, \"restConfig\", e.restConfig)\n\tctx = log.IntoContext(ctx, e.log)\n\tmultiple, _, err := e.target.IsSelectingMultipleInstances(ctx)\n\tif err != nil {\n\t\te.log.Error(err, \"Unable to determine if target resolves to multiple instance\", \"target\", e.target)\n\t\treturn\n\t}\n\tif !multiple {\n\t\tobj, err := e.target.GetReferencedObject(ctx)\n\t\tif err != nil {\n\t\t\te.log.Error(err, \"Unable to get referenced object\", \"target\", e.target)\n\t\t\treturn\n\t\t}\n\t\tsourceName, sourceNamespace, err := e.source.GetNameAndNamespace(ctx, obj)\n\t\tif err != nil {\n\t\t\te.log.Error(err, \"Unable to process name and namespace templates\", \"source\", e.source, \"param\", obj)\n\t\t\treturn\n\t\t}\n\t\tif sourceName == evt.Object.GetName() && sourceNamespace == evt.Object.GetNamespace() {\n\t\t\te.log.V(1).Info(\"enqueing\", \"request\", reconcile.Request{\n\t\t\t\tNamespacedName: types.NamespacedName{\n\t\t\t\t\tName:      e.target.Name,\n\t\t\t\t\tNamespace: e.target.Namespace,\n\t\t\t\t},\n\t\t\t})\n\t\t\tq.Add(reconcile.Request{\n\t\t\t\tNamespacedName: types.NamespacedName{\n\t\t\t\t\tName:      e.target.Name,\n\t\t\t\t\tNamespace: e.target.Namespace,\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t\treturn\n\t}\n\n\tif multiple {\n\t\tobjs, err := e.target.GetReferencedObjects(ctx)\n\t\tif err != nil {\n\t\t\te.log.Error(err, \"Unable to get referenced objects\", \"target\", e.target)\n\t\t\treturn\n\t\t}\n\t\tfor i := range objs {\n\t\t\tsourceName, sourceNamespace, err := e.source.GetNameAndNamespace(ctx, &objs[i])\n\t\t\tif err != nil {\n\t\t\t\te.log.Error(err, \"Unable to process name and namespace templates\", \"source\", e.source, \"param\", objs[i])\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif sourceName == evt.Object.GetName() && sourceNamespace == evt.Object.GetNamespace() {\n\t\t\t\te.log.V(1).Info(\"enqueing\", \"request\", reconcile.Request{\n\t\t\t\t\tNamespacedName: types.NamespacedName{\n\t\t\t\t\t\tName:      objs[i].GetName(),\n\t\t\t\t\t\tNamespace: objs[i].GetNamespace(),\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tq.Add(reconcile.Request{\n\t\t\t\t\tNamespacedName: types.NamespacedName{\n\t\t\t\t\t\tName:      objs[i].GetName(),\n\t\t\t\t\t\tNamespace: objs[i].GetNamespace(),\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n}\n\n// Update implements EventHandler\nfunc (e *enqueueRequestForPatch) Update(ctx context.Context, evt event.UpdateEvent, q workqueue.RateLimitingInterface) {\n\t//to see if this event is relevant and we have to do the following:\n\t// 1. see if the target is single or multiple\n\t// 2. if single just see if it matches, the pass the event.\n\t// 3. if multiple see which macth and then pass the event\n\t// TODO  this could be optmized to see if the change affected the needed jsonpath\n\te.log.V(1).Info(\"enqueue update\", \"for\", evt.ObjectNew)\n\tctx = context.WithValue(ctx, \"discoveryClient\", e.discoveryClient)\n\tctx = context.WithValue(ctx, \"restConfig\", e.restConfig)\n\tctx = log.IntoContext(ctx, e.log)\n\tmultiple, _, err := e.target.IsSelectingMultipleInstances(ctx)\n\tif err != nil {\n\t\te.log.Error(err, \"Unable to determine if target resolves to multiple instance\", \"target\", e.target)\n\t\treturn\n\t}\n\n\tif !multiple {\n\t\tobj, err := e.target.GetReferencedObject(ctx)\n\t\tif err != nil {\n\t\t\te.log.Error(err, \"Unable to get referenced object\", \"target\", e.target)\n\t\t\treturn\n\t\t}\n\t\tsourceName, sourceNamespace, err := e.source.GetNameAndNamespace(ctx, obj)\n\t\tif err != nil {\n\t\t\te.log.Error(err, \"Unable to process name and namespace templates\", \"source\", e.source, \"param\", obj)\n\t\t\treturn\n\t\t}\n\t\tif sourceName == evt.ObjectNew.GetName() && sourceNamespace == evt.ObjectNew.GetNamespace() {\n\t\t\tq.Add(reconcile.Request{\n\t\t\t\tNamespacedName: types.NamespacedName{\n\t\t\t\t\tName:      e.target.Name,\n\t\t\t\t\tNamespace: e.target.Namespace,\n\t\t\t\t},\n\t\t\t})\n\t\t}\n\t\treturn\n\t}\n\n\tif multiple {\n\t\tobjs, err := e.target.GetReferencedObjects(ctx)\n\t\tif err != nil {\n\t\t\te.log.Error(err, \"Unable to get referenced objects\", \"target\", e.target)\n\t\t\treturn\n\t\t}\n\t\tfor i := range objs {\n\t\t\tsourceName, sourceNamespace, err := e.source.GetNameAndNamespace(ctx, &objs[i])\n\t\t\tif err != nil {\n\t\t\t\te.log.Error(err, \"Unable to process name and namespace templates\", \"source\", e.source, \"param\", objs[i])\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif sourceName == evt.ObjectNew.GetName() && sourceNamespace == evt.ObjectNew.GetNamespace() {\n\t\t\t\tq.Add(reconcile.Request{\n\t\t\t\t\tNamespacedName: types.NamespacedName{\n\t\t\t\t\t\tName:      objs[i].GetName(),\n\t\t\t\t\t\tNamespace: objs[i].GetNamespace(),\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Delete implements EventHandler\nfunc (e *enqueueRequestForPatch) Delete(ctx context.Context, evt event.DeleteEvent, q workqueue.RateLimitingInterface) {\n}\n\n// Generic implements EventHandler\nfunc (e *enqueueRequestForPatch) Generic(ctx context.Context, evt event.GenericEvent, q workqueue.RateLimitingInterface) {\n}\n\ntype sourceReferenceModifiedPredicate struct {\n\tsource     *utilsapi.SourceObjectReference\n\ttarget     *utilsapi.TargetObjectReference\n\trestConfig *rest.Config\n\tlog        logr.Logger\n}\n\n// Update implements default UpdateEvent filter for validating resource version change\nfunc (p *sourceReferenceModifiedPredicate) Update(e event.UpdateEvent) bool {\n\t//TODO can be optimized by calculating whether we are selecting multiple objects\n\tp.log.V(1).Info(\"filter update\", \"for\", e.ObjectNew)\n\tctx := context.TODO()\n\tctrl.LoggerInto(ctx, p.log)\n\treturn p.isRelevant(e.ObjectNew) && !compareSourceObjects(ctx, p.source, e.ObjectNew, e.ObjectOld)\n}\n\nfunc (p *sourceReferenceModifiedPredicate) Create(e event.CreateEvent) bool {\n\t//TODO can be optimized by calculating whether we are selecting multiple objects\n\tp.log.V(1).Info(\"filter create\", \"for\", e.Object)\n\treturn p.isRelevant(e.Object)\n}\n\nfunc (p *sourceReferenceModifiedPredicate) isRelevant(obj client.Object) bool {\n\t// we need to aggressively filter events.\n\t// if name and namespaces are not templates, we can check the object\n\tif !strings.Contains(p.source.Name, \"{{\") && !strings.Contains(p.source.Namespace, \"{{\") {\n\t\treturn obj.GetName() == p.source.Name && obj.GetNamespace() == p.source.Namespace\n\t}\n\t// if target is not selecting multiple instances then we can resolve the templates and test the object\n\tctx := context.TODO()\n\tctrl.LoggerInto(ctx, p.log)\n\tctx = context.WithValue(ctx, \"restConfig\", p.restConfig)\n\tmultiple, _, err := p.target.IsSelectingMultipleInstances(ctx)\n\tif err != nil {\n\t\tp.log.Error(err, \"unable to determine if target object selects multiple instances\")\n\t\treturn false\n\t}\n\tif !multiple {\n\t\ttobj, err := p.target.GetReferencedObject(ctx)\n\t\tif err != nil {\n\t\t\tp.log.Error(err, \"unable to get target referenced obect\")\n\t\t\treturn false\n\t\t}\n\t\tname, namespace, err := p.source.GetNameAndNamespace(ctx, tobj)\n\t\tif err != nil {\n\t\t\tp.log.Error(err, \"unable to get source name and namespace from target\")\n\t\t\treturn false\n\t\t}\n\t\treturn name == obj.GetName() && namespace == obj.GetNamespace()\n\t}\n\treturn true\n}\n\nfunc (p *sourceReferenceModifiedPredicate) Delete(e event.DeleteEvent) bool {\n\t// we ignore Delete events because if we loosed references there is no point in trying to recompute the patch\n\treturn false\n}\n\nfunc (p *sourceReferenceModifiedPredicate) Generic(e event.GenericEvent) bool {\n\t// we ignore Generic events\n\treturn false\n}\n\ntype targetReferenceModifiedPredicate struct {\n\tutilsapi.TargetObjectReference\n\trestConfig *rest.Config\n\tlog        logr.Logger\n}\n\n// Update implements default UpdateEvent filter for validating resource version change\nfunc (p *targetReferenceModifiedPredicate) Update(e event.UpdateEvent) bool {\n\tp.log.V(1).Info(\"filter update\", \"for\", e.ObjectNew)\n\tctx := context.TODO()\n\tctrl.LoggerInto(ctx, p.log)\n\tctx = context.WithValue(ctx, \"restConfig\", p.restConfig)\n\tselected, err := p.TargetObjectReference.Selects(ctx, e.ObjectNew)\n\tif err != nil {\n\t\tp.log.Error(err, \"unable to determine if current object is selected\", \"object\", e.ObjectNew, \"target\", p.TargetObjectReference)\n\t\treturn false\n\t}\n\tp.log.V(1).Info(\"\", \"selected\", selected)\n\tif selected {\n\t\treturn !compareObjectsWithoutIgnoredFields(e.ObjectNew, e.ObjectOld)\n\t}\n\treturn false\n}\n\nfunc (p *targetReferenceModifiedPredicate) Create(e event.CreateEvent) bool {\n\tp.log.V(1).Info(\"filter create\", \"for\", e.Object)\n\tctx := context.TODO()\n\tctrl.LoggerInto(ctx, p.log)\n\tctx = context.WithValue(ctx, \"restConfig\", p.restConfig)\n\tselected, err := p.TargetObjectReference.Selects(ctx, e.Object)\n\tif err != nil {\n\t\tp.log.Error(err, \"unable to determine if current object is selected\", \"object\", e.Object, \"target\", p.TargetObjectReference)\n\t\treturn false\n\t}\n\treturn selected\n}\n\nfunc (p *targetReferenceModifiedPredicate) Delete(e event.DeleteEvent) bool {\n\t// we ignore Delete events because if we loosed references there is no point in trying to recompute the patch\n\treturn false\n}\n\nfunc (p *targetReferenceModifiedPredicate) Generic(e event.GenericEvent) bool {\n\t// we ignore Generic events\n\treturn false\n}\n\n// we ignore the fields of resourceVersion and managedFields\nfunc compareObjectsWithoutIgnoredFields(changedObjSrc runtime.Object, originalObjSrc runtime.Object) bool {\n\tchangedObj := changedObjSrc.DeepCopyObject().(*unstructured.Unstructured)\n\toriginalObj := originalObjSrc.DeepCopyObject().(*unstructured.Unstructured)\n\n\tchangedObj.SetManagedFields(nil)\n\tchangedObj.SetResourceVersion(\"\")\n\toriginalObj.SetManagedFields(nil)\n\toriginalObj.SetResourceVersion(\"\")\n\n\tchangedObjJSON, _ := json.Marshal(changedObj)\n\toriginalObjJSON, _ := json.Marshal(originalObj)\n\n\treturn (string(changedObjJSON) == string(originalObjJSON))\n}\n\nfunc compareSourceObjects(ctx context.Context, sourceObjectReference *utilsapi.SourceObjectReference, changedObjSrc runtime.Object, originalObjSrc runtime.Object) bool {\n\tif sourceObjectReference.FieldPath != \"\" {\n\t\tmlog := log.FromContext(ctx)\n\t\tchangedUnstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(changedObjSrc)\n\t\tif err != nil {\n\t\t\tmlog.Error(err, \"unable to convert runtime object to unstructured\", \"runtime object\", changedObjSrc)\n\t\t\treturn false\n\t\t}\n\t\toriginalUnstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(originalObjSrc)\n\t\tif err != nil {\n\t\t\tmlog.Error(err, \"unable to convert runtime object to unstructured\", \"runtime object\", originalObjSrc)\n\t\t\treturn false\n\t\t}\n\t\tchangedObjSubMap, err := getSubMapFromObject(ctx, &unstructured.Unstructured{Object: changedUnstructuredObj}, sourceObjectReference.FieldPath)\n\t\tif err != nil {\n\t\t\tmlog.Error(err, \"unable to convert get submap from unstructured\", \"fieldPath\", sourceObjectReference.FieldPath, \"unstructured\", changedUnstructuredObj)\n\t\t\treturn false\n\t\t}\n\t\toriginalObjSubMap, err := getSubMapFromObject(ctx, &unstructured.Unstructured{Object: originalUnstructuredObj}, sourceObjectReference.FieldPath)\n\t\tif err != nil {\n\t\t\tmlog.Error(err, \"unable to convert get submap from unstructured\", \"fieldPath\", sourceObjectReference.FieldPath, \"unstructured\", originalUnstructuredObj)\n\t\t\treturn false\n\t\t}\n\t\treturn !reflect.DeepEqual(changedObjSubMap, originalObjSubMap)\n\t} else {\n\t\treturn compareObjectsWithoutIgnoredFields(changedObjSrc, originalObjSrc)\n\t}\n}\n\n// Reconcile method\nfunc (lpr *LockedPatchReconciler) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {\n\t//gather all needed the objects\n\tlpr.log.V(1).Info(\"reconcile\", \"for\", request)\n\tctx = context.WithValue(ctx, \"restConfig\", lpr.GetRestConfig())\n\tctx = log.IntoContext(ctx, lpr.log)\n\ttargetObj, err := lpr.patch.TargetObjectRef.GetReferencedObjectWithName(ctx, request.NamespacedName)\n\tif err != nil {\n\t\tlpr.log.Error(err, \"unable to retrieve\", \"target\", lpr.patch.TargetObjectRef)\n\t\treturn lpr.manageErrorNoTarget(err)\n\t}\n\t// the first object is always the target object\n\tsourceMaps := []interface{}{targetObj.UnstructuredContent()}\n\tfor i := range lpr.patch.SourceObjectRefs {\n\t\tsourceObj, err := lpr.patch.SourceObjectRefs[i].GetReferencedObject(ctx, targetObj)\n\t\tif err != nil {\n\t\t\tlpr.log.Error(err, \"unable to retrieve\", \"sourceObjectRef\", lpr.patch.SourceObjectRefs[i])\n\t\t\treturn lpr.manageError(targetObj, err)\n\t\t}\n\t\tsourceMap, err := getSubMapFromObject(ctx, sourceObj, lpr.patch.SourceObjectRefs[i].FieldPath)\n\t\tif err != nil {\n\t\t\tlpr.log.Error(err, \"unable to retrieve\", \"field\", lpr.patch.SourceObjectRefs[i].FieldPath, \"from object\", sourceObj)\n\t\t\treturn lpr.manageError(targetObj, err)\n\t\t}\n\t\tsourceMaps = append(sourceMaps, sourceMap)\n\t}\n\n\t//compute the template\n\tvar b bytes.Buffer\n\terr = lpr.patch.Template.Execute(&b, sourceMaps)\n\tif err != nil {\n\t\tlpr.log.Error(err, \"unable to process \", \"template \", lpr.patch.Template, \"parameters\", sourceMaps)\n\t\treturn lpr.manageError(targetObj, err)\n\t}\n\n\tbb, err := yaml.YAMLToJSON(b.Bytes())\n\n\tif err != nil {\n\t\tlpr.log.Error(err, \"unable to convert to json\", \"processed template\", b.String())\n\t\treturn lpr.manageError(targetObj, err)\n\t}\n\n\tpatch := client.RawPatch(lpr.patch.PatchType, bb)\n\n\terr = lpr.GetClient().Patch(ctx, targetObj, patch)\n\n\tif err != nil {\n\t\tlpr.log.Error(err, \"unable to apply \", \"patch\", patch, \"on target\", targetObj)\n\t\treturn lpr.manageError(targetObj, err)\n\t}\n\n\treturn lpr.manageSuccess(targetObj)\n}\n\n// GetKey return the patch no so unique identifier\nfunc (lpr *LockedPatchReconciler) GetKey() string {\n\treturn lpr.patch.GetKey()\n}\n\nfunc getSubMapFromObject(ctx context.Context, obj *unstructured.Unstructured, fieldPath string) (interface{}, error) {\n\tmlog := log.FromContext(ctx)\n\tif fieldPath == \"\" {\n\t\treturn obj.UnstructuredContent(), nil\n\t}\n\n\tjp := jsonpath.New(\"fieldPath:\" + fieldPath)\n\terr := jp.Parse(\"{\" + fieldPath + \"}\")\n\tif err != nil {\n\t\tmlog.Error(err, \"unable to parse \", \"fieldPath\", fieldPath)\n\t\treturn nil, err\n\t}\n\n\tvalues, err := jp.FindResults(obj.UnstructuredContent())\n\tif err != nil {\n\t\tmlog.Error(err, \"unable to apply \", \"jsonpath\", jp, \" to obj \", obj.UnstructuredContent())\n\t\treturn nil, err\n\t}\n\n\tif len(values) > 0 && len(values[0]) > 0 {\n\t\treturn values[0][0].Interface(), nil\n\t}\n\n\treturn nil, errors.New(\"jsonpath returned empty result\")\n}\n\nfunc (lpr *LockedPatchReconciler) manageError(target client.Object, err error) (reconcile.Result, error) {\n\tcondition := metav1.Condition{\n\t\tType:               apis.ReconcileError,\n\t\tLastTransitionTime: metav1.Now(),\n\t\tMessage:            err.Error(),\n\t\tReason:             apis.ReconcileErrorReason,\n\t\tStatus:             metav1.ConditionTrue,\n\t\tObservedGeneration: target.GetGeneration(),\n\t}\n\tlpr.setStatus(apis.GetKeyShort(target), apis.AddOrReplaceCondition(condition, lpr.GetStatus()[apis.GetKeyShort(target)]))\n\treturn reconcile.Result{}, err\n}\n\nfunc (lpr *LockedPatchReconciler) manageErrorNoTarget(err error) (reconcile.Result, error) {\n\tcondition := metav1.Condition{\n\t\tType:               apis.ReconcileError,\n\t\tLastTransitionTime: metav1.Now(),\n\t\tMessage:            err.Error(),\n\t\tReason:             apis.ReconcileErrorReason,\n\t\tStatus:             metav1.ConditionTrue,\n\t\tObservedGeneration: 0,\n\t}\n\tlpr.setStatus(\"reconciler\", apis.AddOrReplaceCondition(condition, lpr.GetStatus()[\"reconciler\"]))\n\treturn reconcile.Result{}, err\n}\n\nfunc (lpr *LockedPatchReconciler) manageSuccess(target client.Object) (reconcile.Result, error) {\n\tcondition := metav1.Condition{\n\t\tType:               apis.ReconcileSuccess,\n\t\tLastTransitionTime: metav1.Now(),\n\t\tReason:             apis.ReconcileSuccessReason,\n\t\tStatus:             metav1.ConditionTrue,\n\t\tObservedGeneration: target.GetGeneration(),\n\t}\n\tlpr.setStatus(apis.GetKeyShort(target), apis.AddOrReplaceCondition(condition, lpr.GetStatus()[apis.GetKeyShort(target)]))\n\treturn reconcile.Result{}, nil\n}\n\nfunc (lpr *LockedPatchReconciler) setStatus(key string, conditions []metav1.Condition) {\n\tlpr.statusLock.Lock()\n\tdefer lpr.statusLock.Unlock()\n\tlpr.status[key] = conditions\n\tif lpr.statusChange != nil {\n\t\tlpr.statusChange <- event.GenericEvent{\n\t\t\tObject: lpr.parentObject,\n\t\t}\n\t}\n}\n\n// GetStatus returns the status for this reconciler\nfunc (lpr *LockedPatchReconciler) GetStatus() map[string][]metav1.Condition {\n\tlpr.statusLock.Lock()\n\tdefer lpr.statusLock.Unlock()\n\treturn lpr.status\n}\n"
  },
  {
    "path": "pkg/util/lockedresourcecontroller/resource-reconciler.go",
    "content": "package lockedresourcecontroller\n\nimport (\n\t\"context\"\n\t\"reflect\"\n\t\"sync\"\n\n\t\"encoding/json\"\n\n\t\"github.com/go-logr/logr\"\n\n\t\"github.com/nsf/jsondiff\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/apis\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/dynamicclient\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/lockedresourcecontroller/lockedresource\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\tv1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/apimachinery/pkg/types\"\n\tctrl \"sigs.k8s.io/controller-runtime\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller\"\n\t\"sigs.k8s.io/controller-runtime/pkg/event\"\n\t\"sigs.k8s.io/controller-runtime/pkg/handler\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/manager\"\n\t\"sigs.k8s.io/controller-runtime/pkg/predicate\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n\t\"sigs.k8s.io/controller-runtime/pkg/source\"\n)\n\n// LockedResourceReconciler is a reconciler that will lock down a resource to prevent changes from external events.\n// This reconciler can be configured to ignore a set of json path. Changed occurring on the ignored path will be ignored, and therefore allowed by the reconciler\ntype LockedResourceReconciler struct {\n\tResource     unstructured.Unstructured\n\tExcludePaths []string\n\tutil.ReconcilerBase\n\tstatus         []metav1.Condition\n\tstatusChange   chan<- event.GenericEvent\n\tstatusLock     sync.Mutex\n\tparentObject   client.Object\n\tfirstReconcile chan event.GenericEvent\n\tlog            logr.Logger\n}\n\n// NewLockedObjectReconciler returns a new reconcile.Reconciler\nfunc NewLockedObjectReconciler(mgr manager.Manager, object unstructured.Unstructured, excludePaths []string, statusChange chan<- event.GenericEvent, parentObject client.Object) (*LockedResourceReconciler, error) {\n\n\tcontrollername := \"resource-reconciler\"\n\n\treconciler := &LockedResourceReconciler{\n\t\tlog:            ctrl.Log.WithName(controllername).WithName(apis.GetKeyShort(parentObject)).WithName(apis.GetKeyLong(&object)),\n\t\tReconcilerBase: util.NewFromManager(mgr, mgr.GetEventRecorderFor(controllername+\"_\"+apis.GetKeyLong(&object))),\n\t\tResource:       object,\n\t\tExcludePaths:   excludePaths,\n\t\tstatusChange:   statusChange,\n\t\tparentObject:   parentObject,\n\t\tstatusLock:     sync.Mutex{},\n\t\tfirstReconcile: make(chan event.GenericEvent),\n\t\tstatus: []metav1.Condition([]metav1.Condition{{\n\t\t\tType:               \"Initializing\",\n\t\t\tLastTransitionTime: metav1.Now(),\n\t\t\tStatus:             metav1.ConditionTrue,\n\t\t\tObservedGeneration: object.GetGeneration(),\n\t\t\tReason:             \"ReconcilerManagerRestarting\",\n\t\t}}),\n\t}\n\n\tgo func() {\n\t\treconciler.firstReconcile <- event.GenericEvent{\n\t\t\tObject: &object,\n\t\t}\n\t}()\n\n\tcontroller, err := controller.New(\"controller_locked_object_\"+apis.GetKeyLong(&object), mgr, controller.Options{Reconciler: reconciler})\n\tif err != nil {\n\t\treconciler.log.Error(err, \"unable to create new controller\", \"with reconciler\", reconciler)\n\t\treturn &LockedResourceReconciler{}, err\n\t}\n\n\tgvk := object.GetObjectKind().GroupVersionKind()\n\tgroupVersion := schema.GroupVersion{Group: gvk.Group, Version: gvk.Version}\n\n\tmgr.GetScheme().AddKnownTypes(groupVersion, &object)\n\n\terr = controller.Watch(source.Kind(mgr.GetCache(), &object), &handler.EnqueueRequestForObject{}, &resourceModifiedPredicate{\n\t\tname:      object.GetName(),\n\t\tnamespace: object.GetNamespace(),\n\t\tlrr:       reconciler,\n\t})\n\tif err != nil {\n\t\treconciler.log.Error(err, \"unable to create new watch\", \"with source\", object)\n\t\treturn &LockedResourceReconciler{}, err\n\t}\n\n\terr = controller.Watch(\n\t\t&source.Channel{Source: reconciler.firstReconcile},\n\t\t&handler.EnqueueRequestForObject{},\n\t)\n\tif err != nil {\n\t\treturn &LockedResourceReconciler{}, err\n\t}\n\n\treturn reconciler, nil\n}\n\n// Reconcile contains the reconcile logic for LockedResourceReconciler\nfunc (lor *LockedResourceReconciler) Reconcile(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {\n\tlor.log.Info(\"reconcile called for\", \"object\", apis.GetKeyLong(&lor.Resource), \"request\", request)\n\tctx = context.WithValue(ctx, \"restConfig\", lor.GetRestConfig())\n\tctx = log.IntoContext(ctx, lor.log)\n\tclient, err := dynamicclient.GetDynamicClientOnUnstructured(ctx, &lor.Resource)\n\tif err != nil {\n\t\tlor.log.Error(err, \"unable to get dynamicClient\", \"on object\", lor.Resource)\n\t\treturn lor.manageErrorNoInstance(err)\n\t}\n\tinstance, err := client.Get(ctx, lor.Resource.GetName(), v1.GetOptions{})\n\tif err != nil {\n\t\tif apierrors.IsNotFound(err) {\n\t\t\t// if not found we have to recreate it.\n\t\t\terr = lor.CreateOrUpdateResource(ctx, nil, \"\", lor.Resource.DeepCopy())\n\t\t\tif err != nil {\n\t\t\t\tlor.log.Error(err, \"unable to create or update\", \"object\", lor.Resource)\n\t\t\t\treturn lor.manageErrorNoInstance(err)\n\t\t\t}\n\t\t\treturn lor.manageSuccessNoInstance()\n\t\t}\n\t\t// Error reading the object - requeue the request.\n\t\tlor.log.Error(err, \"unable to lookup\", \"object\", lor.Resource)\n\t\treturn lor.manageError(instance, err)\n\t}\n\tequal, err := lor.isEqual(instance)\n\tif err != nil {\n\t\tlor.log.Error(err, \"unable to determine if\", \"object\", lor.Resource, \"is equal to object\", instance)\n\t\treturn lor.manageError(instance, err)\n\t}\n\tif !equal {\n\t\tlor.log.V(1).Info(\"determined that resources are NOT equal\", \"differences\", lor.logDiff(instance))\n\t\tpatch, err := lockedresource.FilterOutPaths(&lor.Resource, lor.ExcludePaths)\n\t\tif err != nil {\n\t\t\tlor.log.Error(err, \"unable to filter out \", \"excluded paths\", lor.ExcludePaths, \"from object\", lor.Resource)\n\t\t\treturn lor.manageError(instance, err)\n\t\t}\n\t\tif err != nil {\n\t\t\tlor.log.Error(err, \"unable to marshall \", \"object\", patch)\n\t\t\treturn lor.manageError(instance, err)\n\t\t}\n\t\tpatchBytes, err := json.Marshal(patch)\n\t\tif err != nil {\n\t\t\tlor.log.Error(err, \"unable to marshall \", \"object\", patch)\n\t\t\treturn lor.manageError(instance, err)\n\t\t}\n\t\t_, err = client.Patch(ctx, instance.GetName(), types.MergePatchType, patchBytes, metav1.PatchOptions{})\n\t\tif err != nil {\n\t\t\tlor.log.Error(err, \"unable to patch \", \"object\", instance, \"with patch\", string(patchBytes))\n\t\t\treturn lor.manageError(instance, err)\n\t\t}\n\t\treturn lor.manageSuccess(instance)\n\t}\n\tlor.log.V(1).Info(\"determined that resources are equal\")\n\treturn lor.manageSuccess(instance)\n}\n\nfunc (lor *LockedResourceReconciler) isEqual(instance *unstructured.Unstructured) (bool, error) {\n\tleft, err := lockedresource.FilterOutPaths(&lor.Resource, lor.ExcludePaths)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\tright, err := lockedresource.FilterOutPaths(instance, lor.ExcludePaths)\n\tif err != nil {\n\t\treturn false, err\n\t}\n\treturn reflect.DeepEqual(left, right), nil\n}\n\nfunc (lor *LockedResourceReconciler) logDiff(instance *unstructured.Unstructured) string {\n\tfi, err := lockedresource.FilterOutPaths(instance, lor.ExcludePaths)\n\tif err != nil {\n\t\treturn \"unable to log differences\"\n\t}\n\tfr, err := lockedresource.FilterOutPaths(&lor.Resource, lor.ExcludePaths)\n\tif err != nil {\n\t\treturn \"unable to log differences\"\n\t}\n\tfib, err := json.Marshal(fi)\n\tif err != nil {\n\t\treturn \"unable to log differences\"\n\t}\n\tfrb, err := json.Marshal(fr)\n\tif err != nil {\n\t\treturn \"unable to log differences\"\n\t}\n\n\topts := jsondiff.DefaultJSONOptions()\n\topts.SkipMatches = true\n\topts.Indent = \"\\t\"\n\t_, diff := jsondiff.Compare(fib, frb, &opts)\n\treturn diff\n}\n\ntype resourceModifiedPredicate struct {\n\tname      string\n\tnamespace string\n\tlrr       *LockedResourceReconciler\n\tpredicate.Funcs\n}\n\n// Update implements default UpdateEvent filter for validating resource version change\nfunc (p *resourceModifiedPredicate) Update(e event.UpdateEvent) bool {\n\tif e.ObjectNew.GetNamespace() == p.namespace && e.ObjectNew.GetName() == p.name {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (p *resourceModifiedPredicate) Create(e event.CreateEvent) bool {\n\tif e.Object.GetNamespace() == p.namespace && e.Object.GetName() == p.name {\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (p *resourceModifiedPredicate) Delete(e event.DeleteEvent) bool {\n\tif e.Object.GetNamespace() == p.namespace && e.Object.GetName() == p.name {\n\t\t// we return true only if the enclosing namespace is not also being deleted\n\t\tif e.Object.GetNamespace() != \"\" {\n\t\t\tnamespace := corev1.Namespace{}\n\t\t\t// Use non-cached client since client's cache may be namespaced\n\t\t\terr := p.lrr.GetAPIReader().Get(context.TODO(), types.NamespacedName{Name: e.Object.GetNamespace()}, &namespace)\n\t\t\tif err != nil {\n\t\t\t\tp.lrr.log.Error(err, \"unable to retrieve \", \"namespace\", \"e.Meta.GetNamespace()\")\n\t\t\t\t// If the request failed return \"true\" as the k8s API will deny any create/update operation in a\n\t\t\t\t// Namespace that's marked for termination. Returning false here causes resources not being reconciled\n\t\t\t\t// in namespaced installations (Namespace requires a client with cluster scoped permissions)\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tif util.IsBeingDeleted(&namespace) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t}\n\t\treturn true\n\t}\n\treturn false\n}\n\nfunc (lor *LockedResourceReconciler) manageError(instance *unstructured.Unstructured, err error) (reconcile.Result, error) {\n\tcondition := metav1.Condition{\n\t\tType:               apis.ReconcileError,\n\t\tLastTransitionTime: metav1.Now(),\n\t\tMessage:            err.Error(),\n\t\tReason:             apis.ReconcileErrorReason,\n\t\tStatus:             metav1.ConditionTrue,\n\t\tObservedGeneration: func() int64 {\n\t\t\tif instance != nil {\n\t\t\t\treturn instance.GetGeneration()\n\t\t\t} else {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t}(),\n\t}\n\tlor.setStatus(apis.AddOrReplaceCondition(condition, lor.GetStatus()))\n\treturn reconcile.Result{}, err\n}\n\nfunc (lor *LockedResourceReconciler) manageErrorNoInstance(err error) (reconcile.Result, error) {\n\tcondition := metav1.Condition{\n\t\tType:               apis.ReconcileError,\n\t\tLastTransitionTime: metav1.Now(),\n\t\tMessage:            err.Error(),\n\t\tReason:             apis.ReconcileErrorReason,\n\t\tStatus:             metav1.ConditionTrue,\n\t\tObservedGeneration: 0,\n\t}\n\tlor.setStatus(apis.AddOrReplaceCondition(condition, lor.GetStatus()))\n\treturn reconcile.Result{}, err\n}\n\nfunc (lor *LockedResourceReconciler) manageSuccess(instance *unstructured.Unstructured) (reconcile.Result, error) {\n\tcondition := metav1.Condition{\n\t\tType:               apis.ReconcileSuccess,\n\t\tLastTransitionTime: metav1.Now(),\n\t\tReason:             apis.ReconcileSuccessReason,\n\t\tStatus:             metav1.ConditionTrue,\n\t\tObservedGeneration: instance.GetGeneration(),\n\t}\n\tlor.setStatus(apis.AddOrReplaceCondition(condition, lor.GetStatus()))\n\treturn reconcile.Result{}, nil\n}\n\nfunc (lor *LockedResourceReconciler) manageSuccessNoInstance() (reconcile.Result, error) {\n\tcondition := metav1.Condition{\n\t\tType:               apis.ReconcileSuccess,\n\t\tLastTransitionTime: metav1.Now(),\n\t\tReason:             apis.ReconcileSuccessReason,\n\t\tStatus:             metav1.ConditionTrue,\n\t\tObservedGeneration: 0,\n\t}\n\tlor.setStatus(apis.AddOrReplaceCondition(condition, lor.GetStatus()))\n\treturn reconcile.Result{}, nil\n}\n\nfunc (lor *LockedResourceReconciler) setStatus(status []metav1.Condition) {\n\tlor.statusLock.Lock()\n\tdefer lor.statusLock.Unlock()\n\tlor.status = status\n\tif lor.statusChange != nil {\n\t\tlor.statusChange <- event.GenericEvent{\n\t\t\tObject: lor.parentObject,\n\t\t}\n\t}\n}\n\n// GetStatus returns the latest reconcile status\nfunc (lor *LockedResourceReconciler) GetStatus() []metav1.Condition {\n\tlor.statusLock.Lock()\n\tdefer lor.statusLock.Unlock()\n\tstatus := lor.status\n\treturn status\n}\n"
  },
  {
    "path": "pkg/util/owner.go",
    "content": "/*\nCopyright 2019 Red Hat, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage util\n\nimport (\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime\"\n)\n\nfunc IsOwner(owner, owned metav1.Object) bool {\n\truntimeObj, ok := (owner).(runtime.Object)\n\tif !ok {\n\t\treturn false\n\t}\n\tfor _, ownerRef := range owned.GetOwnerReferences() {\n\t\tif ownerRef.Name == owner.GetName() && ownerRef.UID == owner.GetUID() && ownerRef.Kind == runtimeObj.GetObjectKind().GroupVersionKind().Kind {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "pkg/util/predicates.go",
    "content": "/*\nCopyright 2019 Red Hat, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage util\n\nimport (\n\t\"reflect\"\n\n\t\"sigs.k8s.io/controller-runtime/pkg/event\"\n\t\"sigs.k8s.io/controller-runtime/pkg/predicate\"\n)\n\n// ResourceGenerationOrFinalizerChangedPredicate this predicate will fire an update event when the spec of a resource is changed (controller by ResourceGeneration), or when the finalizers are changed\ntype ResourceGenerationOrFinalizerChangedPredicate struct {\n\tpredicate.Funcs\n}\n\n// Update implements default UpdateEvent filter for validating resource version change\nfunc (ResourceGenerationOrFinalizerChangedPredicate) Update(e event.UpdateEvent) bool {\n\tif e.ObjectOld == nil {\n\t\treturn false\n\t}\n\tif e.ObjectNew == nil {\n\t\treturn false\n\t}\n\tif e.ObjectNew.GetGeneration() == e.ObjectOld.GetGeneration() && reflect.DeepEqual(e.ObjectNew.GetFinalizers(), e.ObjectOld.GetFinalizers()) {\n\t\treturn false\n\t}\n\treturn true\n}\n"
  },
  {
    "path": "pkg/util/reconciler.go",
    "content": "/*\nCopyright 2019 Red Hat, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage util\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io/ioutil\"\n\t\"os\"\n\t\"text/template\"\n\t\"time\"\n\n\t\"github.com/redhat-cop/operator-utils/pkg/util/apis\"\n\t\"github.com/redhat-cop/operator-utils/pkg/util/templates\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\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/types\"\n\t\"k8s.io/client-go/discovery\"\n\tclientgoscheme \"k8s.io/client-go/kubernetes/scheme\"\n\t\"k8s.io/client-go/rest\"\n\t\"k8s.io/client-go/tools/record\"\n\t\"sigs.k8s.io/controller-runtime/pkg/client\"\n\t\"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/manager\"\n\t\"sigs.k8s.io/controller-runtime/pkg/reconcile\"\n)\n\n// ReconcilerBase is a base struct from which all reconcilers can be derived from. By doing so your reconcilers will also inherit a set of utility functions\n// To inherit from reconciler just build your finalizer this way:\n//\n//\ttype MyReconciler struct {\n//\t  util.ReconcilerBase\n//\t  ... other optional fields ...\n//\t}\ntype ReconcilerBase struct {\n\tapireader  client.Reader\n\tclient     client.Client\n\tscheme     *runtime.Scheme\n\trestConfig *rest.Config\n\trecorder   record.EventRecorder\n}\n\nfunc NewReconcilerBase(client client.Client, scheme *runtime.Scheme, restConfig *rest.Config, recorder record.EventRecorder, apireader client.Reader) ReconcilerBase {\n\treturn ReconcilerBase{\n\t\tapireader:  apireader,\n\t\tclient:     client,\n\t\tscheme:     scheme,\n\t\trestConfig: restConfig,\n\t\trecorder:   recorder,\n\t}\n}\n\n// NewReconcilerBase is a contruction function to create a new ReconcilerBase.\nfunc NewFromManager(mgr manager.Manager, recorder record.EventRecorder) ReconcilerBase {\n\treturn NewReconcilerBase(mgr.GetClient(), mgr.GetScheme(), mgr.GetConfig(), recorder, mgr.GetAPIReader())\n}\n\n// IsValid determines if a CR instance is valid. this implementation returns always true, should be overridden\nfunc (r *ReconcilerBase) IsValid(obj metav1.Object) (bool, error) {\n\treturn true, nil\n}\n\n// IsInitialized determines if a CR instance is initialized. this implementation returns always true, should be overridden\nfunc (r *ReconcilerBase) IsInitialized(obj metav1.Object) bool {\n\treturn true\n}\n\n// Reconcile is a stub function to have ReconsicerBase match the Reconciler interface. You must redefine this function\nfunc (r *ReconcilerBase) Reconcile(request reconcile.Request) (reconcile.Result, error) {\n\treturn reconcile.Result{}, nil\n}\n\n// GetClient returns the underlying client\nfunc (r *ReconcilerBase) GetClient() client.Client {\n\treturn r.client\n}\n\n// GetRestConfig returns the undelying rest config\nfunc (r *ReconcilerBase) GetRestConfig() *rest.Config {\n\treturn r.restConfig\n}\n\n// GetRecorder returns the underlying recorder\nfunc (r *ReconcilerBase) GetRecorder() record.EventRecorder {\n\treturn r.recorder\n}\n\n// GetScheme returns the scheme\nfunc (r *ReconcilerBase) GetScheme() *runtime.Scheme {\n\treturn r.scheme\n}\n\n// GetDiscoveryClient returns a discovery client for the current reconciler\nfunc (r *ReconcilerBase) GetDiscoveryClient() (*discovery.DiscoveryClient, error) {\n\treturn discovery.NewDiscoveryClientForConfig(r.GetRestConfig())\n}\n\n// CreateOrUpdateResource creates a resource if it doesn't exist, and updates (overwrites it), if it exist\n// if owner is not nil, the owner field os set\n// if namespace is not \"\", the namespace field of the object is overwritten with the passed value\nfunc (r *ReconcilerBase) CreateOrUpdateResource(context context.Context, owner client.Object, namespace string, obj client.Object) error {\n\tlog := log.FromContext(context)\n\tif owner != nil {\n\t\t_ = controllerutil.SetControllerReference(owner, obj, r.GetScheme())\n\t}\n\tif namespace != \"\" {\n\t\tobj.SetNamespace(namespace)\n\t}\n\n\tobj2 := &unstructured.Unstructured{}\n\tobj2.SetGroupVersionKind(obj.GetObjectKind().GroupVersionKind())\n\n\terr := r.GetClient().Get(context, types.NamespacedName{\n\t\tNamespace: obj.GetNamespace(),\n\t\tName:      obj.GetName(),\n\t}, obj2)\n\n\tif apierrors.IsNotFound(err) {\n\t\terr = r.GetClient().Create(context, obj)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to create object\", \"object\", obj)\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\t}\n\tif err == nil {\n\t\tobj.SetResourceVersion(obj2.GetResourceVersion())\n\t\terr = r.GetClient().Update(context, obj)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to update object\", \"object\", obj)\n\t\t\treturn err\n\t\t}\n\t\treturn nil\n\n\t}\n\tlog.Error(err, \"unable to lookup object\", \"object\", obj)\n\treturn err\n}\n\n// CreateOrUpdateResources operates as CreateOrUpdate, but on an array of resources\nfunc (r *ReconcilerBase) CreateOrUpdateResources(context context.Context, owner client.Object, namespace string, objs []client.Object) error {\n\tfor _, obj := range objs {\n\t\terr := r.CreateOrUpdateResource(context, owner, namespace, obj)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// CreateOrUpdateUnstructuredResources operates as CreateOrUpdate, but on an array of unstructured.Unstructured\nfunc (r *ReconcilerBase) CreateOrUpdateUnstructuredResources(context context.Context, owner client.Object, namespace string, objs []unstructured.Unstructured) error {\n\tfor _, obj := range objs {\n\t\terr := r.CreateOrUpdateResource(context, owner, namespace, &obj)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// DeleteResourceIfExists deletes an existing resource. It doesn't fail if the resource does not exist\nfunc (r *ReconcilerBase) DeleteResourceIfExists(context context.Context, obj client.Object) error {\n\tlog := log.FromContext(context)\n\terr := r.GetClient().Delete(context, obj)\n\tif err != nil && !apierrors.IsNotFound(err) {\n\t\tlog.Error(err, \"unable to delete object \", \"object\", obj)\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// DeleteResourcesIfExist operates like DeleteResources, but on an arrays of resources\nfunc (r *ReconcilerBase) DeleteResourcesIfExist(context context.Context, objs []client.Object) error {\n\tfor _, obj := range objs {\n\t\terr := r.DeleteResourceIfExists(context, obj)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// DeleteUnstructuredResources operates like DeleteResources, but on an arrays of unstructured.Unstructured\nfunc (r *ReconcilerBase) DeleteUnstructuredResources(context context.Context, objs []unstructured.Unstructured) error {\n\tfor _, obj := range objs {\n\t\terr := r.DeleteResourceIfExists(context, &obj)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// CreateResourceIfNotExists create a resource if it doesn't already exists. If the resource exists it is left untouched and the functin does not fails\n// if owner is not nil, the owner field os set\n// if namespace is not \"\", the namespace field of the object is overwritten with the passed value\nfunc (r *ReconcilerBase) CreateResourceIfNotExists(context context.Context, owner client.Object, namespace string, obj client.Object) error {\n\tlog := log.FromContext(context)\n\tif owner != nil {\n\t\t_ = controllerutil.SetControllerReference(owner, obj, r.GetScheme())\n\t}\n\tif namespace != \"\" {\n\t\tobj.SetNamespace(namespace)\n\t}\n\n\terr := r.GetClient().Create(context, obj)\n\tif err != nil && !apierrors.IsAlreadyExists(err) {\n\t\tlog.Error(err, \"unable to create object \", \"object\", obj)\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// CreateResourcesIfNotExist operates as CreateResourceIfNotExists, but on an array of resources\nfunc (r *ReconcilerBase) CreateResourcesIfNotExist(context context.Context, owner client.Object, namespace string, objs []client.Object) error {\n\tfor _, obj := range objs {\n\t\terr := r.CreateResourceIfNotExists(context, owner, namespace, obj)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// CreateUnstructuredResourcesIfNotExist operates as CreateResourceIfNotExists, but on an array of unstructured.Unstructured\nfunc (r *ReconcilerBase) CreateUnstructuredResourcesIfNotExist(context context.Context, owner client.Object, namespace string, objs []unstructured.Unstructured) error {\n\tfor _, obj := range objs {\n\t\terr := r.CreateResourceIfNotExists(context, owner, namespace, &obj)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// CreateOrUpdateTemplatedResources processes an initialized template expecting an array of objects as a result and the processes them with the CreateOrUpdate function\nfunc (r *ReconcilerBase) CreateOrUpdateTemplatedResources(context context.Context, owner client.Object, namespace string, data interface{}, template *template.Template) error {\n\tlog := log.FromContext(context)\n\tobjs, err := templates.ProcessTemplateArray(context, data, template)\n\tif err != nil {\n\t\tlog.Error(err, \"error creating manifest from template\")\n\t\treturn err\n\t}\n\tfor _, obj := range objs {\n\t\terr = r.CreateOrUpdateResource(context, owner, namespace, &obj)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// CreateIfNotExistTemplatedResources processes an initialized template expecting an array of objects as a result and then processes them with the CreateResourceIfNotExists function\nfunc (r *ReconcilerBase) CreateIfNotExistTemplatedResources(context context.Context, owner client.Object, namespace string, data interface{}, template *template.Template) error {\n\tlog := log.FromContext(context)\n\tobjs, err := templates.ProcessTemplateArray(context, data, template)\n\tif err != nil {\n\t\tlog.Error(err, \"error creating manifest from template\")\n\t\treturn err\n\t}\n\tfor _, obj := range objs {\n\t\terr = r.CreateResourceIfNotExists(context, owner, namespace, &obj)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// DeleteTemplatedResources processes an initialized template expecting an array of objects as a result and then processes them with the Delete function\nfunc (r *ReconcilerBase) DeleteTemplatedResources(context context.Context, data interface{}, template *template.Template) error {\n\tlog := log.FromContext(context)\n\tobjs, err := templates.ProcessTemplateArray(context, data, template)\n\tif err != nil {\n\t\tlog.Error(err, \"error creating manifest from template\")\n\t\treturn err\n\t}\n\tfor _, obj := range objs {\n\t\terr = r.DeleteResourceIfExists(context, &obj)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\n// ManageOutcomeWithRequeue is a convenience function to call either ManageErrorWithRequeue if issue is non-nil, else ManageSuccessWithRequeue\nfunc (r *ReconcilerBase) ManageOutcomeWithRequeue(context context.Context, obj client.Object, issue error, requeueAfter time.Duration) (reconcile.Result, error) {\n\tif issue != nil {\n\t\treturn r.ManageErrorWithRequeue(context, obj, issue, requeueAfter)\n\t}\n\treturn r.ManageSuccessWithRequeue(context, obj, requeueAfter)\n}\n\n// ManageErrorWithRequeue will take care of the following:\n// 1. generate a warning event attached to the passed CR\n// 2. set the status of the passed CR to a error condition if the object implements the apis.ConditionsStatusAware interface\n// 3. return a reconcile status with with the passed requeueAfter and error\nfunc (r *ReconcilerBase) ManageErrorWithRequeue(context context.Context, obj client.Object, issue error, requeueAfter time.Duration) (reconcile.Result, error) {\n\tlog := log.FromContext(context)\n\tr.GetRecorder().Event(obj, \"Warning\", \"ProcessingError\", issue.Error())\n\tif conditionsAware, updateStatus := (obj).(apis.ConditionsAware); updateStatus {\n\t\tcondition := metav1.Condition{\n\t\t\tType:               apis.ReconcileError,\n\t\t\tLastTransitionTime: metav1.Now(),\n\t\t\tObservedGeneration: obj.GetGeneration(),\n\t\t\tMessage:            issue.Error(),\n\t\t\tReason:             apis.ReconcileErrorReason,\n\t\t\tStatus:             metav1.ConditionTrue,\n\t\t}\n\t\tconditionsAware.SetConditions(apis.AddOrReplaceCondition(condition, conditionsAware.GetConditions()))\n\t\terr := r.GetClient().Status().Update(context, obj)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to update status\")\n\t\t\treturn reconcile.Result{RequeueAfter: requeueAfter}, err\n\t\t}\n\t} else {\n\t\tlog.V(1).Info(\"object is not ConditionsAware, not setting status\")\n\t}\n\treturn reconcile.Result{RequeueAfter: requeueAfter}, issue\n}\n\n// ManageError will take care of the following:\n// 1. generate a warning event attached to the passed CR\n// 2. set the status of the passed CR to a error condition if the object implements the apis.ConditionsStatusAware interface\n// 3. return a reconcile status with the passed error\nfunc (r *ReconcilerBase) ManageError(context context.Context, obj client.Object, issue error) (reconcile.Result, error) {\n\treturn r.ManageErrorWithRequeue(context, obj, issue, 0)\n}\n\n// ManageSuccessWithRequeue will update the status of the CR and return a successful reconcile result with requeueAfter set\nfunc (r *ReconcilerBase) ManageSuccessWithRequeue(context context.Context, obj client.Object, requeueAfter time.Duration) (reconcile.Result, error) {\n\tlog := log.FromContext(context)\n\tif conditionsAware, updateStatus := (obj).(apis.ConditionsAware); updateStatus {\n\t\tcondition := metav1.Condition{\n\t\t\tType:               apis.ReconcileSuccess,\n\t\t\tLastTransitionTime: metav1.Now(),\n\t\t\tObservedGeneration: obj.GetGeneration(),\n\t\t\tReason:             apis.ReconcileSuccessReason,\n\t\t\tStatus:             metav1.ConditionTrue,\n\t\t}\n\t\tconditionsAware.SetConditions(apis.AddOrReplaceCondition(condition, conditionsAware.GetConditions()))\n\t\terr := r.GetClient().Status().Update(context, obj)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"unable to update status\")\n\t\t\treturn reconcile.Result{RequeueAfter: requeueAfter}, err\n\t\t}\n\t} else {\n\t\tlog.V(1).Info(\"object is not ConditionsAware, not setting status\")\n\t}\n\treturn reconcile.Result{RequeueAfter: requeueAfter}, nil\n}\n\n// ManageSuccess will update the status of the CR and return a successful reconcile result\nfunc (r *ReconcilerBase) ManageSuccess(context context.Context, obj client.Object) (reconcile.Result, error) {\n\treturn r.ManageSuccessWithRequeue(context, obj, 0)\n}\n\n// GetDirectClient returns a non cached client\nfunc (r *ReconcilerBase) GetDirectClient() (client.Client, error) {\n\treturn r.GetDirectClientWithSchemeBuilders()\n}\n\n// GetDirectClientWithSchemeBuilders returns a non cached client initialized with the scheme.buidlers passed as parameters\nfunc (r *ReconcilerBase) GetDirectClientWithSchemeBuilders(addToSchemes ...func(s *runtime.Scheme) error) (client.Client, error) {\n\tscheme := runtime.NewScheme()\n\tfor _, addToscheme := range append(addToSchemes, clientgoscheme.AddToScheme) {\n\t\terr := addToscheme(scheme)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t}\n\treturn client.New(r.GetRestConfig(), client.Options{\n\t\tScheme: scheme,\n\t})\n}\n\n// GetAPIReader returns a non cached reader\nfunc (r *ReconcilerBase) GetAPIReader() client.Reader {\n\treturn r.apireader\n}\n\n// GetOperatorNamespace tries to infer the operator namespace. I first looks for the /var/run/secrets/kubernetes.io/serviceaccount/namespace file.\n// Then it looks for a NAMESPACE environment variable (useful when running in local mode).\nfunc (r *ReconcilerBase) GetOperatorNamespace() (string, error) {\n\tvar namespaceFilePath = \"/var/run/secrets/kubernetes.io/serviceaccount/namespace\"\n\tb, err := ioutil.ReadFile(namespaceFilePath)\n\tif err != nil {\n\t\tnamespace, ok := os.LookupEnv(\"NAMESPACE\")\n\t\tif !ok {\n\t\t\treturn \"\", errors.New(\"unable to infer namespace in which operator is running\")\n\t\t}\n\t\treturn namespace, nil\n\t}\n\treturn string(b), nil\n}\n"
  },
  {
    "path": "pkg/util/stoppablemanager/stoppable-manager.go",
    "content": "package stoppablemanager\n\nimport (\n\t\"context\"\n\t\"errors\"\n\n\t\"k8s.io/client-go/rest\"\n\tlogf \"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/controller-runtime/pkg/manager\"\n)\n\nvar log = logf.Log.WithName(\"stoppable_manager\")\n\n// StoppableManager A StoppableManaager allows you to easily create controller-runtim.Managers that can be started and stopped.\ntype StoppableManager struct {\n\tstarted bool\n\tmanager.Manager\n\tcancelFunction context.CancelFunc\n}\n\n// Stop stops the manager\nfunc (sm *StoppableManager) Stop() {\n\tif !sm.started {\n\t\tlog.Error(errors.New(\"invalid argument\"), \"stop called on a non started channel\", \"started\", sm.started)\n\t\treturn\n\t}\n\tsm.cancelFunction()\n\t//close(sm.stopChannel)\n\tsm.started = false\n}\n\n// Start starts the manager. Restarting a starated manager is a noop that will be logged.\nfunc (sm *StoppableManager) Start(parentCtx context.Context) {\n\tif sm.started {\n\t\tlog.Error(errors.New(\"invalid argument\"), \"start called on a started channel\")\n\t\treturn\n\t}\n\tctx, cancel := context.WithCancel(parentCtx)\n\tsm.cancelFunction = cancel\n\tgo func() {\n\t\terr := sm.Manager.Start(ctx)\n\t\tif err != nil {\n\t\t\tlog.Error(errors.New(\"unable to start manager\"), \"unable to start manager\")\n\t\t}\n\t}()\n\tsm.started = true\n}\n\n// NewStoppableManager creates a new stoppable manager\nfunc NewStoppableManager(config *rest.Config, options manager.Options) (StoppableManager, error) {\n\tmanager, err := manager.New(config, options)\n\tif err != nil {\n\t\treturn StoppableManager{}, err\n\t}\n\treturn StoppableManager{\n\t\tManager: manager,\n\t}, nil\n}\n\n// IsStarted returns wether this stoppable manager is running.\nfunc (sm *StoppableManager) IsStarted() bool {\n\treturn sm.started\n}\n"
  },
  {
    "path": "pkg/util/templates/advanced-funcmap.go",
    "content": "/*\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\n/*\nConcept pulled from Helm to match a known templating pattern\nhttps://github.com/helm/helm/blob/master/pkg/engine/funcs.go\n*/\n\npackage templates\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"strings\"\n\t\"text/template\"\n\n\t\"github.com/BurntSushi/toml\"\n\t\"github.com/Masterminds/sprig/v3\"\n\t\"github.com/go-logr/logr\"\n\t\"github.com/pkg/errors\"\n\n\t\"github.com/redhat-cop/operator-utils/pkg/util/dynamicclient\"\n\tapierrors \"k8s.io/apimachinery/pkg/api/errors\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/apimachinery/pkg/runtime/schema\"\n\t\"k8s.io/client-go/dynamic\"\n\t\"k8s.io/client-go/rest\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/yaml\"\n)\n\n// AdvancedTemplateFuncMap to add Sprig and additional templating functions\nfunc AdvancedTemplateFuncMap(config *rest.Config, logger logr.Logger) template.FuncMap {\n\tf := sprig.HermeticTxtFuncMap()\n\t// Removed these functions from the core Sprig package for security concerns\n\tdelete(f, \"env\")\n\tdelete(f, \"expandenv\")\n\n\textra := template.FuncMap{\n\t\t\"toToml\":        toTOML,\n\t\t\"toYaml\":        toYAML,\n\t\t\"fromYaml\":      fromYAML,\n\t\t\"fromYamlArray\": fromYAMLArray,\n\t\t\"toJson\":        toJSON,\n\t\t\"fromJson\":      fromJSON,\n\t\t\"fromJsonArray\": fromJSONArray,\n\n\t\t// A variety of known templating functions that have not been implemented yet\n\t\t\"include\":  func(string, interface{}) string { return \"not implemented\" },\n\t\t\"tpl\":      func(string, interface{}) interface{} { return \"not implemented\" },\n\t\t\"required\": func(string, interface{}) (interface{}, error) { return \"not implemented\", nil },\n\t}\n\n\tfor k, v := range extra {\n\t\tf[k] = v\n\t}\n\n\t// Adding additional functionality found in Helm\n\tf[\"lookup\"] = NewLookupFunction(config, logger)\n\n\t// Add the `required` function here so we can use lintMode\n\tf[\"required\"] = func(warn string, val interface{}) (interface{}, error) {\n\t\tif val == nil {\n\t\t\treturn val, errors.Errorf(warn)\n\t\t} else if _, ok := val.(string); ok {\n\t\t\tif val == \"\" {\n\t\t\t\treturn val, errors.Errorf(warn)\n\t\t\t}\n\t\t}\n\t\treturn val, nil\n\t}\n\n\treturn f\n}\n\n// toYAML takes an interface, marshals it to yaml, and returns a string. It will\n// always return a string, even on marshal error (empty string).\n//\n// This is designed to be called from a template.\nfunc toYAML(v interface{}) string {\n\tdata, err := yaml.Marshal(v)\n\tif err != nil {\n\t\t// Swallow errors inside of a template.\n\t\treturn \"\"\n\t}\n\treturn strings.TrimSuffix(string(data), \"\\n\")\n}\n\n// fromYAML converts a YAML document into a map[string]interface{}.\n//\n// This is not a general-purpose YAML parser, and will not parse all valid\n// YAML documents. Additionally, because its intended use is within templates\n// it tolerates errors. It will insert the returned error message string into\n// m[\"Error\"] in the returned map.\nfunc fromYAML(str string) map[string]interface{} {\n\tm := map[string]interface{}{}\n\n\tif err := yaml.Unmarshal([]byte(str), &m); err != nil {\n\t\tm[\"Error\"] = err.Error()\n\t}\n\treturn m\n}\n\n// fromYAMLArray converts a YAML array into a []interface{}.\n//\n// This is not a general-purpose YAML parser, and will not parse all valid\n// YAML documents. Additionally, because its intended use is within templates\n// it tolerates errors. It will insert the returned error message string as\n// the first and only item in the returned array.\nfunc fromYAMLArray(str string) []interface{} {\n\ta := []interface{}{}\n\n\tif err := yaml.Unmarshal([]byte(str), &a); err != nil {\n\t\ta = []interface{}{err.Error()}\n\t}\n\treturn a\n}\n\n// toTOML takes an interface, marshals it to toml, and returns a string. It will\n// always return a string, even on marshal error (empty string).\n//\n// This is designed to be called from a template.\nfunc toTOML(v interface{}) string {\n\tb := bytes.NewBuffer(nil)\n\te := toml.NewEncoder(b)\n\terr := e.Encode(v)\n\tif err != nil {\n\t\treturn err.Error()\n\t}\n\treturn b.String()\n}\n\n// toJSON takes an interface, marshals it to json, and returns a string. It will\n// always return a string, even on marshal error (empty string).\n//\n// This is designed to be called from a template.\nfunc toJSON(v interface{}) string {\n\tdata, err := json.Marshal(v)\n\tif err != nil {\n\t\t// Swallow errors inside of a template.\n\t\treturn \"\"\n\t}\n\treturn string(data)\n}\n\n// fromJSON converts a JSON document into a map[string]interface{}.\n//\n// This is not a general-purpose JSON parser, and will not parse all valid\n// JSON documents. Additionally, because its intended use is within templates\n// it tolerates errors. It will insert the returned error message string into\n// m[\"Error\"] in the returned map.\nfunc fromJSON(str string) map[string]interface{} {\n\tm := make(map[string]interface{})\n\n\tif err := json.Unmarshal([]byte(str), &m); err != nil {\n\t\tm[\"Error\"] = err.Error()\n\t}\n\treturn m\n}\n\n// fromJSONArray converts a JSON array into a []interface{}.\n//\n// This is not a general-purpose JSON parser, and will not parse all valid\n// JSON documents. Additionally, because its intended use is within templates\n// it tolerates errors. It will insert the returned error message string as\n// the first and only item in the returned array.\nfunc fromJSONArray(str string) []interface{} {\n\ta := []interface{}{}\n\n\tif err := json.Unmarshal([]byte(str), &a); err != nil {\n\t\ta = []interface{}{err.Error()}\n\t}\n\treturn a\n}\n\ntype lookupFunc = func(apiversion string, resource string, namespace string, name string) (map[string]interface{}, error)\n\n// NewLookupFunction get information at runtime from cluster\nfunc NewLookupFunction(config *rest.Config, logger logr.Logger) lookupFunc {\n\treturn func(apiversion string, resource string, namespace string, name string) (map[string]interface{}, error) {\n\t\tvar client dynamic.ResourceInterface\n\t\tctx := context.TODO()\n\t\tctx = context.WithValue(ctx, \"restConfig\", config)\n\t\tctx = log.IntoContext(ctx, logger.WithName(\"lookup function\"))\n\t\tc, namespaced, err := dynamicclient.GetDynamicClientForGVK(ctx, schema.FromAPIVersionAndKind(apiversion, resource))\n\t\tif err != nil {\n\t\t\treturn map[string]interface{}{}, err\n\t\t}\n\t\tif namespaced && namespace != \"\" {\n\t\t\tclient = c.Namespace(namespace)\n\t\t} else {\n\t\t\tclient = c\n\t\t}\n\t\tif name != \"\" {\n\t\t\t// this will return a single object\n\t\t\tobj, err := client.Get(ctx, name, metav1.GetOptions{})\n\t\t\tif err != nil {\n\t\t\t\tif apierrors.IsNotFound(err) {\n\t\t\t\t\t// Just return an empty interface when the object was not found.\n\t\t\t\t\t// That way, users can use `if not (lookup ...)` in their templates.\n\t\t\t\t\treturn map[string]interface{}{}, nil\n\t\t\t\t}\n\t\t\t\treturn map[string]interface{}{}, err\n\t\t\t}\n\t\t\treturn obj.UnstructuredContent(), nil\n\t\t}\n\t\t//this will return a list\n\t\tobj, err := client.List(ctx, metav1.ListOptions{})\n\t\tif err != nil {\n\t\t\tif apierrors.IsNotFound(err) {\n\t\t\t\t// Just return an empty interface when the object was not found.\n\t\t\t\t// That way, users can use `if not (lookup ...)` in their templates.\n\t\t\t\treturn map[string]interface{}{}, nil\n\t\t\t}\n\t\t\treturn map[string]interface{}{}, err\n\t\t}\n\t\treturn obj.UnstructuredContent(), nil\n\t}\n}\n"
  },
  {
    "path": "pkg/util/templates/templates.go",
    "content": "/*\nCopyright 2019 Red Hat, Inc.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\npackage templates\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"text/template\"\n\n\t\"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured\"\n\t\"k8s.io/kubectl/pkg/validation\"\n\t\"sigs.k8s.io/controller-runtime/pkg/log\"\n\t\"sigs.k8s.io/yaml\"\n)\n\n// ProcessTemplate processes an initialized Go template with a set of data. It expects one API object to be defined in the template\n// requires a context with log\nfunc ProcessTemplate(context context.Context, data interface{}, template *template.Template) (*unstructured.Unstructured, error) {\n\tlog := log.FromContext(context)\n\tobj := unstructured.Unstructured{}\n\tvar b bytes.Buffer\n\terr := template.Execute(&b, data)\n\tif err != nil {\n\t\tlog.Error(err, \"Error executing template\", \"template\", template)\n\t\treturn &obj, err\n\t}\n\n\tbb, err := yaml.YAMLToJSON(b.Bytes())\n\tif err != nil {\n\t\tlog.Error(err, \"Error transforming yaml to json\", \"manifest\", b.String())\n\t\treturn &obj, err\n\t}\n\n\terr = obj.UnmarshalJSON(bb)\n\tif err != nil {\n\t\tlog.Error(err, \"Error unmarshalling json manifest\", \"manifest\", string(bb))\n\t\treturn &obj, err\n\t}\n\treturn &obj, err\n}\n\n// ProcessTemplateArray processes an initialized Go template with a set of data. It expects an arrays of API objects to be defined in the template. Dishomogeneus types are supported\n// requires a context with log\nfunc ProcessTemplateArray(context context.Context, data interface{}, template *template.Template) ([]unstructured.Unstructured, error) {\n\tlog := log.FromContext(context)\n\tobjs := []unstructured.Unstructured{}\n\tvar b bytes.Buffer\n\terr := template.Execute(&b, data)\n\tif err != nil {\n\t\tlog.Error(err, \"Error executing template\", \"template\", template)\n\t\treturn []unstructured.Unstructured{}, err\n\t}\n\tbb, err := yaml.YAMLToJSON(b.Bytes())\n\tif err != nil {\n\t\tlog.Error(err, \"Error transforming yaml to json\", \"manifest\", b.String())\n\t\treturn []unstructured.Unstructured{}, err\n\t}\n\tif !IsJSONArray(bb) {\n\t\tobj := unstructured.Unstructured{}\n\t\terr = obj.UnmarshalJSON(bb)\n\t\tobjs = append(objs, obj)\n\t} else {\n\t\tintfs := &[]interface{}{}\n\t\terr = json.Unmarshal(bb, &intfs)\n\t\tif err != nil {\n\t\t\tlog.Error(err, \"Error unmarshalling json manifest\", \"manifest\", string(bb))\n\t\t\treturn []unstructured.Unstructured{}, err\n\t\t}\n\t\tfor _, intf := range *intfs {\n\t\t\tb, err := json.Marshal(intf)\n\t\t\tif err != nil {\n\t\t\t\tlog.Error(err, \"Error marshalling\", \"interface\", intf)\n\t\t\t\treturn []unstructured.Unstructured{}, err\n\t\t\t}\n\t\t\tobj := unstructured.Unstructured{}\n\t\t\terr = obj.UnmarshalJSON(b)\n\t\t\tif err != nil {\n\t\t\t\tlog.Error(err, \"Error unmarshalling\", \"json\", string(b))\n\t\t\t\treturn []unstructured.Unstructured{}, err\n\t\t\t}\n\t\t\tobjs = append(objs, obj)\n\t\t}\n\t}\n\n\tif err != nil {\n\t\tlog.Error(err, \"Error unmarshalling json manifest\", \"manifest\", string(bb))\n\t\treturn []unstructured.Unstructured{}, err\n\t}\n\treturn objs, err\n}\n\n// ValidateUnstructured validates the content of an unstructured against an openapi schema.\n// the schema is intended to be retrieved from a running instance of kubernetes, but other usages are possible.\n// requires a context with log\nfunc ValidateUnstructured(context context.Context, obj *unstructured.Unstructured, validationSchema validation.Schema) error {\n\tlog := log.FromContext(context)\n\tbb, err := obj.MarshalJSON()\n\tif err != nil {\n\t\tlog.Error(err, \"unable to unmarshall\", \"unstructured\", obj)\n\t\treturn err\n\t}\n\terr = validationSchema.ValidateBytes(bb)\n\tif err != nil {\n\t\tlog.Error(err, \"unable to validate\", \"json doc\", string(bb), \"against schemas\", validationSchema)\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// IsJSONArray checks to see if a byte array containing JSON is an array of data\nfunc IsJSONArray(data []byte) bool {\n\tfirstLine := bytes.TrimLeft(data, \" \\t\\r\\n\")\n\treturn firstLine[0] == '['\n}\n"
  },
  {
    "path": "test/enforcing-patch-multiple-cluster-level.yaml",
    "content": "# test multiple instances cluster\napiVersion: operator-utils.example.io/v1alpha1\nkind: EnforcingPatch\nmetadata:\n  name: test-patch-multiple-cluster-level\nspec:\n  patches:\n  - name: ciao1\n    targetObjectRef:\n      apiVersion: v1\n      kind: Namespace\n    patchTemplate: |\n      metadata:\n        annotations:\n          {{ (index . 0).metadata.uid }}: {{ (index . 1) }}\n    patchType: application/strategic-merge-patch+json\n    sourceObjectRefs:\n    - apiVersion: v1\n      kind: ServiceAccount\n      name: default\n      namespace: \"{{ .metadata.name }}\"\n      fieldPath: $.metadata.uid "
  },
  {
    "path": "test/enforcing-patch-multiple.yaml",
    "content": "#test multiple instances namespaced\napiVersion: operator-utils.example.io/v1alpha1\nkind: EnforcingPatch\nmetadata:\n  name: test-patch-multiple\nspec:\n  patches:\n  - name: ciao1\n    targetObjectRef:\n      apiVersion: v1\n      kind: ServiceAccount\n      name: deployer\n    patchTemplate: |\n      metadata:\n        annotations:\n          {{ (index . 0).metadata.uid }}: {{ (index . 1) }}\n    patchType: application/strategic-merge-patch+json\n    sourceObjectRefs:\n    - apiVersion: v1\n      kind: ServiceAccount\n      name: default\n      namespace: \"{{ .metadata.namespace }}\"\n      fieldPath: $.metadata.uid   "
  },
  {
    "path": "test/enforcing-patch.yaml",
    "content": "\n# # test single instance namespaced\napiVersion: operator-utils.example.io/v1alpha1\nkind: EnforcingPatch\nmetadata:\n  name: test-field-patch\nspec:\n  patches:\n    test-field-patch: \n      targetObjectRef:\n        apiVersion: v1\n        kind: ServiceAccount\n        name: test\n        namespace: patch-test\n      patchTemplate: |\n        metadata:\n          annotations:\n            {{ (index . 1) }}: {{ (index . 2) }}\n      patchType: application/strategic-merge-patch+json\n      sourceObjectRefs:\n      - apiVersion: v1\n        kind: Namespace\n        name: default\n        fieldPath: $.metadata.uid\n      - apiVersion: v1\n        kind: ServiceAccount\n        name: default\n        namespace: default\n        fieldPath: $.metadata.uid  "
  },
  {
    "path": "test/enforcing_cr.yaml",
    "content": "apiVersion: operator-utils.example.io/v1alpha1\nkind: EnforcingCRD\nmetadata:\n  name: example-enforcingcrd\nspec:\n  resources:\n    - object: \n        apiVersion: v1\n        kind: ConfigMap\n        metadata:\n          creationTimestamp: \"2020-03-30T16:24:08Z\"\n          name: test-configmap\n          namespace: test-enforcingcrd\n        data:\n          ciao: ciao\n    - object:\n        apiVersion: route.openshift.io/v1\n        kind: Route\n        metadata:\n          name: test-route\n          namespace: test-enforcingcrd\n        spec:\n          host: grafana-istio-system.apps.cluster-4cac.sandbox456.opentlc.com\n          tls:\n            termination: reencrypt\n          to:\n            kind: Service\n            name: grafana\n            weight: 100\n          wildcardPolicy: None    \n"
  },
  {
    "path": "test/failing-enforcing_cr.yaml",
    "content": "apiVersion: operator-utils.example.io/v1alpha1\nkind: EnforcingCRD\nmetadata:\n  name: example-enforcingcrd2\nspec:\n  resources:\n    - object: \n        apiVersion: v1\n        kind: ConfigMap\n        metadata:\n          creationTimestamp: \"2020-03-30T16:24:08Z\"\n          name: test-configmap-FALING\n          namespace: test-enforcingcrd\n        data:\n          ciao: ciao\n    - object:\n        apiVersion: route.openshift.io/v1\n        kind: Route\n        metadata:\n          name: test-route-FAILING\n          namespace: test-enforcingcrd\n        spec:\n          host: grafana-istio-system.apps.cluster-4cac.sandbox456.opentlc.com\n          tls:\n            termination: reencrypt\n          to:\n            kind: Service\n            name: grafana\n            weight: 100\n          wildcardPolicy: None       "
  },
  {
    "path": "test/mycrd_cr.yaml",
    "content": "apiVersion: operator-utils.example.io/v1alpha1\nkind: MyCRD\nmetadata:\n  name: example-mycrd\nspec:\n  # Add fields here\n  initialized: false\n  valid: true\n  error: false\n"
  },
  {
    "path": "test/templatedenforcing_cr.yaml",
    "content": "apiVersion: operator-utils.example.io/v1alpha1\nkind: TemplatedEnforcingCRD\nmetadata:\n  name: example-enforcingcrd\nspec:\n  templates:\n    - objectTemplate: | \n        apiVersion: v1\n        kind: ConfigMap\n        metadata:\n          creationTimestamp: \"2020-03-30T16:24:08Z\"\n          name: test-configmap\n          namespace: {{ .Namespace }}\n        data:\n          ciao: ciao\n    - objectTemplate: |\n        apiVersion: route.openshift.io/v1\n        kind: Route\n        metadata:\n          name: test-route\n          namespace: {{ .Namespace }}\n        spec:\n          host: grafana-istio-system.apps.cluster-4cac.sandbox456.opentlc.com\n          tls:\n            termination: reencrypt\n          to:\n            kind: Service\n            name: grafana\n            weight: 100\n          wildcardPolicy: None"
  },
  {
    "path": "testbin/setup-envtest.sh",
    "content": "#!/usr/bin/env bash\n\n#  Copyright 2020 The Kubernetes Authors.\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\nset -o errexit\nset -o pipefail\n\n# Turn colors in this script off by setting the NO_COLOR variable in your\n# environment to any value:\n#\n# $ NO_COLOR=1 test.sh\nNO_COLOR=${NO_COLOR:-\"\"}\nif [ -z \"$NO_COLOR\" ]; then\n  header=$'\\e[1;33m'\n  reset=$'\\e[0m'\nelse\n  header=''\n  reset=''\nfi\n\nfunction header_text {\n  echo \"$header$*$reset\"\n}\n\nfunction setup_envtest_env {\n  header_text \"setting up env vars\"\n\n  # Setup env vars\n  KUBEBUILDER_ASSETS=${KUBEBUILDER_ASSETS:-\"\"}\n  if [[ -z \"${KUBEBUILDER_ASSETS}\" ]]; then\n    export KUBEBUILDER_ASSETS=$1/bin\n  fi\n}\n\n# fetch k8s API gen tools and make it available under envtest_root_dir/bin.\n#\n# Skip fetching and untaring the tools by setting the SKIP_FETCH_TOOLS variable\n# in your environment to any value:\n#\n# $ SKIP_FETCH_TOOLS=1 ./check-everything.sh\n#\n# If you skip fetching tools, this script will use the tools already on your\n# machine.\nfunction fetch_envtest_tools {\n  SKIP_FETCH_TOOLS=${SKIP_FETCH_TOOLS:-\"\"}\n  if [ -n \"$SKIP_FETCH_TOOLS\" ]; then\n    return 0\n  fi\n\n  tmp_root=/tmp\n  envtest_root_dir=$tmp_root/envtest\n\n  k8s_version=\"${ENVTEST_K8S_VERSION:-1.16.4}\"\n  goarch=\"$(go env GOARCH)\"\n  goos=\"$(go env GOOS)\"\n\n  if [[ \"$goos\" != \"linux\" && \"$goos\" != \"darwin\" ]]; then\n    echo \"OS '$goos' not supported. Aborting.\" >&2\n    return 1\n  fi\n\n  local dest_dir=\"${1}\"\n\n  # use the pre-existing version in the temporary folder if it matches our k8s version\n  if [[ -x \"${dest_dir}/bin/kube-apiserver\" ]]; then\n    version=$(\"${dest_dir}\"/bin/kube-apiserver --version)\n    if [[ $version == *\"${k8s_version}\"* ]]; then\n      header_text \"Using cached envtest tools from ${dest_dir}\"\n      return 0\n    fi\n  fi\n\n  header_text \"fetching envtest tools@${k8s_version} (into '${dest_dir}')\"\n  envtest_tools_archive_name=\"kubebuilder-tools-$k8s_version-$goos-$goarch.tar.gz\"\n  envtest_tools_download_url=\"https://storage.googleapis.com/kubebuilder-tools/$envtest_tools_archive_name\"\n\n  envtest_tools_archive_path=\"$tmp_root/$envtest_tools_archive_name\"\n  if [ ! -f $envtest_tools_archive_path ]; then\n    curl -sL ${envtest_tools_download_url} -o \"$envtest_tools_archive_path\"\n  fi\n\n  mkdir -p \"${dest_dir}\"\n  tar -C \"${dest_dir}\" --strip-components=1 -zvxf \"$envtest_tools_archive_path\"\n}\n"
  }
]