[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [estahn]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\notechie: # Replace with a single Otechie username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    target-branch: \"main\"\n    schedule:\n      interval: \"weekly\"\n\n  - package-ecosystem: \"docker\"\n    directory: \"/\"\n    target-branch: \"main\"\n    schedule:\n      interval: \"daily\"\n\n  - package-ecosystem: \"gomod\"\n    directory: \"/\"\n    target-branch: \"main\"\n    schedule:\n      interval: \"weekly\"\n\n  - package-ecosystem: \"npm\"\n    directory: \"/\"\n    target-branch: \"main\"\n    schedule:\n      interval: \"weekly\"\n    versioning-strategy: increase\n"
  },
  {
    "path": ".github/release-drafter.yml",
    "content": "name-template: 'v$RESOLVED_VERSION'\ntag-template: 'v$RESOLVED_VERSION'\ncategories:\n  - title: '🚀 Features'\n    labels:\n      - 'feature'\n      - 'enhancement'\n  - title: '🐛 Bug Fixes'\n    labels:\n      - 'fix'\n      - 'bugfix'\n      - 'bug'\n  - title: '📝 Documentation'\n    label: 'docs'\n  - title: '🧰 Maintenance'\n    label: 'chore'\n  - title: '⬆️ Dependencies'\n    collapse-after: 3\n    labels:\n      - 'dependencies'\n  - title: '👷 Continuous Integration'\n    collapse-after: 3\n    labels:\n      - 'ci'\n\nexclude-labels:\n  - 'ignore-for-release'\n\nreplacers:\n  - search: '/^(fix|feat|ci|build)(\\(.+?\\))?: /g'\n    replace: ''\n\ntemplate: |\n  ## What's Changed\n\n  $CHANGES\n\nversion-resolver:\n  major:\n    labels:\n      - 'type: breaking'\n  minor:\n    labels:\n      - 'enhancement'\n  patch:\n    labels:\n      - 'bugfix'\n      - 'maintenance'\n      - 'docs'\n      - 'dependencies'\n      - 'security'\n\nautolabeler:\n  - label: 'bugfix'\n    title:\n      - '/fix:/i'\n  - label: 'enhancement'\n    title:\n      - '/feat:/i'\n  - label: 'docs'\n    title:\n      - '/docs:/i'\n  - label: 'chore'\n    title:\n      - '/chore:/i'\n"
  },
  {
    "path": ".github/release.yml",
    "content": "changelog:\n  exclude:\n    labels:\n      - ignore-for-release\n    authors:\n      - octocat\n  categories:\n    - title: 🛠 Breaking Changes\n      labels:\n        - breaking-change\n    - title: '🚀 Features'\n      labels:\n        - 'feature'\n        - 'enhancement'\n    - title: '🐛 Bug Fixes'\n      labels:\n        - 'fix'\n        - 'bugfix'\n        - 'bug'\n    - title: '📝 Documentation'\n      label: 'docs'\n    - title: '🧰 Maintenance'\n      label: 'chore'\n    - title: '⬆️ Dependencies'\n      collapse-after: 3\n      labels:\n        - 'dependencies'\n    - title: '👷 Continuous Integration'\n      collapse-after: 3\n      labels:\n        - 'ci'\n    - title: Other Changes\n      labels:\n        - \"*\"\n"
  },
  {
    "path": ".github/workflows/auto-approve.yml",
    "content": "name: Auto approve\n\non:\n  pull_request_target\n\njobs:\n  auto-approve:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: hmarr/auto-approve-action@v4\n      if: github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]'\n      with:\n        github-token: \"${{ secrets.GITHUB_TOKEN }}\"\n"
  },
  {
    "path": ".github/workflows/auto-merge.yml",
    "content": "name: Auto-Merge\non: pull_request\n\npermissions:\n  pull-requests: write\n  contents: write\n\njobs:\n  automerge:\n    runs-on: ubuntu-latest\n    if: github.actor == 'dependabot[bot]'\n    steps:\n      - uses: peter-evans/enable-pull-request-automerge@v3\n        with:\n          pull-request-number: ${{ github.event.pull_request.number }}\n          merge-method: squash\n"
  },
  {
    "path": ".github/workflows/awaiting-reply.yml",
    "content": "on:\n  issue_comment:\n    types: [created]\n\njobs:\n  awaiting_reply:\n    runs-on: ubuntu-latest\n    name: Toggle label upon reply\n    steps:\n      - name: Toggle label\n        uses: jd-0001/gh-action-toggle-awaiting-reply-label@v2.1.2\n        with:\n          label: awaiting-reply\n          exclude-members: estahn\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "# For most projects, this workflow file will not need changing; you simply need\n# to commit it to your repository.\n#\n# You may wish to alter this file to override the set of languages analyzed,\n# or to provide custom queries or build logic.\n#\n# ******** NOTE ********\n# We have attempted to detect the languages in your repository. Please check\n# the `language` matrix defined below to confirm you have the correct set of\n# supported CodeQL languages.\n#\nname: \"CodeQL\"\n\non:\n  push:\n    branches: [ main ]\n    paths-ignore:\n      - 'docs/**'\n      - 'mkdocs.yml'\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches: [ main ]\n  schedule:\n    - cron: '42 5 * * 5'\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n\n    strategy:\n      fail-fast: false\n      matrix:\n        language: [ 'go' ]\n        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]\n        # Learn more:\n        # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed\n\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v6\n\n    - name: Set up Go\n      uses: actions/setup-go@v6\n      with:\n        go-version-file: 'go.mod'\n        check-latest: true\n        cache: true\n\n    # Initializes the CodeQL tools for scanning.\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v4\n      with:\n        languages: ${{ matrix.language }}\n        # If you wish to specify custom queries, you can do so here or in a config file.\n        # By default, queries listed here will override any specified in a config file.\n        # Prefix the list here with \"+\" to use these queries and those in the config file.\n        # queries: ./path/to/local/query, your-org/your-repo/queries@main\n\n    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).\n    # If this step fails, then you should remove it and run the build manually (see below)\n    - name: Autobuild\n      uses: github/codeql-action/autobuild@v4\n\n    # ℹ️ Command-line programs to run using the OS shell.\n    # 📚 https://git.io/JvXDl\n\n    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines\n    #    and modify them (or add more) to build your code if your project\n    #    uses a compiled language\n\n    #- run: |\n    #   make bootstrap\n    #   make release\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v4\n"
  },
  {
    "path": ".github/workflows/deploy.yml",
    "content": "name: Deploy\n\non:\n  workflow_call:\n    inputs:\n      forRef:\n        required: true\n        type: string\n  workflow_dispatch:\n    inputs:\n      forRef:\n        description: 'Branch, SHA or Tag to release'\n        required: false\n        type: string\n\npermissions:\n  contents: write\n  packages: write\n\nenv:\n  REGISTRY: ghcr.io\n  IMAGE_NAME: ${{ github.repository }}\n\njobs:\n  generate-artifacts:\n    name: Generate artifacts\n    runs-on: ubuntu-latest\n    steps:\n    - name: Checkout\n      uses: actions/checkout@v6\n      with:\n        ref: ${{ inputs.forRef }}\n\n    - name: Unshallow\n      run: git fetch --prune --unshallow\n\n    - name: Ensure release-notes exists\n      run: touch /tmp/release-notes.md\n\n    - name: Set up QEMU\n      uses: docker/setup-qemu-action@v4\n\n    - name: Set up Docker Buildx\n      id: buildx\n      uses: docker/setup-buildx-action@v3\n\n    - name: Install dependencies\n      run: sudo apt-get update && sudo apt-get install -y libdevmapper-dev libbtrfs-dev\n\n    - name: Set up Go\n      uses: actions/setup-go@v6\n      with:\n        go-version-file: 'go.mod'\n        check-latest: true\n        cache: true\n\n    - name: Login to github registry\n      uses: docker/login-action@v4.1.0\n      with:\n        registry: ${{ env.REGISTRY }}\n        username: ${{ github.actor }}\n        password: ${{ secrets.GITHUB_TOKEN }}\n\n    - name: Run GoReleaser\n      uses: goreleaser/goreleaser-action@v7.0.0\n      with:\n        version: latest\n        args: release --clean\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/docs.yml",
    "content": "#name: Publish docs\n#on:\n#  workflow_dispatch:\n#  push:\n#    branches:\n#      - main\n#    paths:\n#      - 'docs/**'\n#      - mkdocs.yml\n#\n#jobs:\n#  build:\n#    name: Deploy docs\n#    runs-on: ubuntu-latest\n#    steps:\n#      - name: Checkout main\n#        uses: actions/checkout@v3\n#        with:\n#          fetch-depth: 0\n#\n#      - uses: actions/setup-python@v4.5.0\n#        with:\n#          python-version: '3.x'\n#\n#      - name: Install mkdocs\n#        run: pip install --upgrade pip && pip install mike mkdocs mkdocs-minify-plugin mkdocs-markdownextradata-plugin mkdocs-macros-plugin pymdown-extensions mkdocs-material\n#\n#      - run: git config user.name 'github-actions[bot]' && git config user.email 'github-actions[bot]@users.noreply.github.com'\n#\n#      - name: Publish docs\n#        run: mkdocs gh-deploy\n"
  },
  {
    "path": ".github/workflows/pre-commit.yml",
    "content": "name: pre-commit\n\non:\n  pull_request:\n  push:\n    branches: [master]\n\njobs:\n  pre-commit:\n    runs-on: ubuntu-latest\n\n    # don't run this on the master branch\n    if: github.ref != 'refs/heads/master'\n\n    steps:\n      - uses: actions/checkout@v6\n        with:\n          fetch-depth: 0\n      - uses: actions/setup-python@v6.2.0\n        with:\n          python-version: '3.x'\n      - uses: actions/setup-go@v6\n        with:\n          go-version-file: 'go.mod'\n          check-latest: true\n          cache: true\n      - name: Install dependencies\n        run: sudo apt-get update && sudo apt-get install -y libdevmapper-dev libbtrfs-dev\n      - uses: pre-commit/action@v3.0.1\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/release-drafter.yml",
    "content": "name: Release Drafter\n\non:\n  workflow_dispatch:\n  push:\n    # branches to consider in the event; optional, defaults to all\n    branches:\n      - main\n  # pull_request event is required only for autolabeler\n  pull_request:\n    # Only following types are handled by the action, but one can default to all as well\n    types: [opened, reopened, synchronize]\n  # pull_request_target event is required for autolabeler to support PRs from forks\n  pull_request_target:\n    types: [opened, reopened, synchronize]\n\npermissions:\n  contents: read\n\njobs:\n  update_release_draft:\n    permissions:\n      # write permission is required to create a github release\n      contents: write\n      # write permission is required for autolabeler\n      # otherwise, read permission is required at least\n      pull-requests: write\n    runs-on: ubuntu-latest\n    steps:\n      # (Optional) GitHub Enterprise requires GHE_HOST variable set\n      #- name: Set GHE_HOST\n      #  run: |\n      #    echo \"GHE_HOST=${GITHUB_SERVER_URL##https:\\/\\/}\" >> $GITHUB_ENV\n\n      # Drafts your next Release notes as Pull Requests are merged into \"master\"\n      - uses: release-drafter/release-drafter@v7\n        # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml\n        # with:\n        #   config-name: my-config.yml\n        #   disable-autolabeler: true\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\non:\n  workflow_dispatch:\n  # Release patches and secruity updates on a schedule\n  schedule:\n    - cron: \"0 0 1 * *\"\n\njobs:\n  release:\n    permissions:\n      contents: write\n      pull-requests: write\n    runs-on: ubuntu-latest\n    outputs:\n      tag_name: ${{ steps.release-drafter.outputs.tag_name }}\n    steps:\n      - id: release-drafter\n        uses: release-drafter/release-drafter@v7\n        with:\n          publish: true\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n  deploy:\n    needs: [release]\n    uses: ./.github/workflows/deploy.yml\n    secrets: inherit\n    permissions:\n      packages: write\n      contents: write\n    with:\n      forRef: ${{ needs.release.outputs.tag_name }}\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Test\non:\n  pull_request:\n  workflow_dispatch:\n  push:\n    branches:\n      - main\n      - 'releases/*'\n    paths-ignore:\n      - 'docs/**'\n      - 'mkdocs.yml'\n\nconcurrency:\n  group: ${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n\n  lint:\n    name: Lint\n    runs-on: ubuntu-latest\n    steps:\n\n    - name: Install dependencies\n      run: sudo apt-get update && sudo apt-get install -y libdevmapper-dev libbtrfs-dev\n\n    - name: Checkout\n      uses: actions/checkout@v6\n\n    - name: Set up Go\n      uses: actions/setup-go@v6\n      with:\n        go-version-file: 'go.mod'\n        check-latest: true\n        cache: true\n\n    - name: golangci-lint\n      uses: golangci/golangci-lint-action@v9.2.0\n      with:\n        version: latest\n        args: --timeout=5m\n\n  test:\n    name: Test\n    runs-on: ubuntu-latest\n    steps:\n    - name: Install dependencies\n      run: sudo apt-get update && sudo apt-get install -y libdevmapper-dev libbtrfs-dev\n\n    - name: Checkout\n      uses: actions/checkout@v6\n\n    - name: Set up Go\n      uses: actions/setup-go@v6\n      with:\n        go-version-file: 'go.mod'\n        check-latest: true\n        cache: true\n\n    - uses: actions/cache@v5.0.4\n      with:\n        path: |\n          ~/go/pkg/mod              # Module download cache\n          ~/.cache/go-build         # Build cache (Linux)\n          ~/Library/Caches/go-build # Build cache (Mac)\n          '%LocalAppData%\\go-build' # Build cache (Windows)\n        key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}\n        restore-keys: |\n          ${{ runner.os }}-go-\n\n    - name: Test\n      run: go test -coverprofile cover.out ./...\n\n    - uses: codecov/codecov-action@v6\n      with:\n        token: ${{ secrets.CODECOV_TOKEN }}\n        file: ./cover.out # optional\n        fail_ci_if_error: true\n        verbose: true\n\n  image-scan:\n    name: Image Scan\n    runs-on: ubuntu-latest\n    steps:\n\n    - name: Install dependencies\n      run: sudo apt-get update && sudo apt-get install -y libdevmapper-dev libbtrfs-dev\n\n    - name: Checkout\n      uses: actions/checkout@v6\n\n    - name: Set up QEMU\n      uses: docker/setup-qemu-action@v4\n\n    - name: Set up Docker Buildx\n      id: buildx\n      uses: docker/setup-buildx-action@v3\n\n    - name: Unshallow\n      run: git fetch --prune --unshallow\n\n    - name: Set up Go\n      uses: actions/setup-go@v6\n      with:\n        go-version-file: 'go.mod'\n        check-latest: true\n        cache: true\n\n    - uses: actions/cache@v5.0.4\n      with:\n        path: |\n          ~/go/pkg/mod              # Module download cache\n          ~/.cache/go-build         # Build cache (Linux)\n          ~/Library/Caches/go-build # Build cache (Mac)\n          '%LocalAppData%\\go-build' # Build cache (Windows)\n        key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}\n        restore-keys: |\n          ${{ runner.os }}-go-\n\n    - name: Run GoReleaser\n      uses: goreleaser/goreleaser-action@v7.0.0\n      with:\n        version: latest\n        args: release --clean --skip=validate,publish\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n    - name: Scan image\n      uses: anchore/scan-action@v7\n      id: scan\n      with:\n        image: \"ghcr.io/estahn/k8s-image-swapper:latest\"\n        fail-build: false\n        acs-report-enable: true\n\n    - name: Upload Anchore scan SARIF report\n      uses: github/codeql-action/upload-sarif@v4\n      with:\n        sarif_file: ${{ steps.scan.outputs.sarif }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, built with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n# Dependency directories (remove the comment below to include it)\n# vendor/\n\n.idea/\ncoverage.txt\nk8s-image-swapper\n"
  },
  {
    "path": ".goreleaser.yml",
    "content": "env:\n  - GO111MODULE=on\n\ngomod:\n  proxy: true\n\nbuilds:\n  - env:\n      - CGO_ENABLED=0\n    goos:\n      #- windows\n      - darwin\n      - linux\n    goarch:\n      - amd64\n      - arm64\n    mod_timestamp: '{{ .CommitTimestamp }}'\n    flags:\n      - -trimpath\n    ldflags:\n      - -s -w\n\ndockers:\n  - image_templates:\n      - \"ghcr.io/estahn/k8s-image-swapper:latest-amd64\"\n      - \"ghcr.io/estahn/k8s-image-swapper:{{ .Version }}-amd64\"\n      - \"ghcr.io/estahn/k8s-image-swapper:{{ .Major }}.{{ .Minor }}.{{ .Patch }}-amd64\"\n      - \"ghcr.io/estahn/k8s-image-swapper:{{ .Major }}.{{ .Minor }}-amd64\"\n      - \"ghcr.io/estahn/k8s-image-swapper:{{ .Major }}-amd64\"\n    use: buildx\n    dockerfile: Dockerfile\n    goarch: amd64\n    build_flag_templates:\n      - \"--pull\"\n      - \"--label=org.opencontainers.image.created={{.Date}}\"\n      - \"--label=org.opencontainers.image.title={{.ProjectName}}\"\n      - \"--label=org.opencontainers.image.revision={{.FullCommit}}\"\n      - \"--label=org.opencontainers.image.version={{.Version}}\"\n      - \"--build-arg=VERSION={{.Version}}\"\n      - \"--build-arg=BUILD_DATE={{.Date}}\"\n      - \"--build-arg=VCS_REF={{.FullCommit}}\"\n      - \"--platform=linux/amd64\"\n  - image_templates:\n      - \"ghcr.io/estahn/k8s-image-swapper:latest-arm64v8\"\n      - \"ghcr.io/estahn/k8s-image-swapper:{{ .Version }}-arm64v8\"\n      - \"ghcr.io/estahn/k8s-image-swapper:{{ .Major }}.{{ .Minor }}.{{ .Patch }}-arm64v8\"\n      - \"ghcr.io/estahn/k8s-image-swapper:{{ .Major }}.{{ .Minor }}-arm64v8\"\n      - \"ghcr.io/estahn/k8s-image-swapper:{{ .Major }}-arm64v8\"\n    use: buildx\n    dockerfile: Dockerfile\n    goarch: arm64\n    build_flag_templates:\n    - \"--pull\"\n    - \"--label=org.opencontainers.image.created={{.Date}}\"\n    - \"--label=org.opencontainers.image.title={{.ProjectName}}\"\n    - \"--label=org.opencontainers.image.revision={{.FullCommit}}\"\n    - \"--label=org.opencontainers.image.version={{.Version}}\"\n    - \"--build-arg=VERSION={{.Version}}\"\n    - \"--build-arg=BUILD_DATE={{.Date}}\"\n    - \"--build-arg=VCS_REF={{.FullCommit}}\"\n    - \"--platform=linux/arm64/v8\"\n\ndocker_manifests:\n  - name_template: ghcr.io/estahn/k8s-image-swapper:latest\n    image_templates:\n    - ghcr.io/estahn/k8s-image-swapper:latest-amd64\n    - ghcr.io/estahn/k8s-image-swapper:latest-arm64v8\n  - name_template: ghcr.io/estahn/k8s-image-swapper:{{ .Version }}\n    image_templates:\n    - ghcr.io/estahn/k8s-image-swapper:{{ .Version }}-amd64\n    - ghcr.io/estahn/k8s-image-swapper:{{ .Version }}-arm64v8\n  - name_template: ghcr.io/estahn/k8s-image-swapper:{{ .Major }}.{{ .Minor }}.{{ .Patch }}\n    image_templates:\n    - ghcr.io/estahn/k8s-image-swapper:{{ .Major }}.{{ .Minor }}.{{ .Patch }}-amd64\n    - ghcr.io/estahn/k8s-image-swapper:{{ .Major }}.{{ .Minor }}.{{ .Patch }}-arm64v8\n  - name_template: ghcr.io/estahn/k8s-image-swapper:{{ .Major }}.{{ .Minor }}\n    image_templates:\n    - ghcr.io/estahn/k8s-image-swapper:{{ .Major }}.{{ .Minor }}-amd64\n    - ghcr.io/estahn/k8s-image-swapper:{{ .Major }}.{{ .Minor }}-arm64v8\n  - name_template: ghcr.io/estahn/k8s-image-swapper:{{ .Major }}\n    image_templates:\n    - ghcr.io/estahn/k8s-image-swapper:{{ .Major }}-amd64\n    - ghcr.io/estahn/k8s-image-swapper:{{ .Major }}-arm64v8\n\nrelease:\n  prerelease: auto\n\nchangelog:\n  filters:\n    exclude:\n      - '^docs:'\n      - '^chore:'\n\narchives:\n  - format: binary\n"
  },
  {
    "path": ".k8s-image-swapper.yml",
    "content": "dryRun: true\n\nlogLevel: trace\nlogFormat: console\n\n# imageSwapPolicy defines the mutation strategy used by the webhook.\n# - always: Will always swap the image regardless of the image existence in the target registry.\n#           This can result in pods ending in state ImagePullBack if images fail to be copied to the target registry.\n# - exists: Only swaps the image if it exits in the target registry.\n#           This can result in pods pulling images from the source registry, e.g. the first pod pulls\n#           from source registry, subsequent pods pull from target registry.\nimageSwapPolicy: exists\n\n# imageCopyPolicy defines the image copy strategy used by the webhook.\n# - delayed: Submits the copy job to a process queue and moves on.\n# - immediate: Submits the copy job to a process queue and waits for it to finish (deadline 8s).\n# - force: Attempts to immediately copy the image (deadline 8s).\n# - none: Do not copy the image.\nimageCopyPolicy: delayed\n\nsource:\n  # Filters provide control over what pods will be processed.\n  # By default all pods will be processed. If a condition matches, the pod will NOT be processed.\n  # For query language details see https://jmespath.org/\n  filters:\n    # Do not process if namespace equals \"kube-system\"\n    - jmespath: \"obj.metadata.namespace == 'kube-system'\"\n\n    # Only process if namespace equals \"playground\"\n    #- jmespath: \"obj.metadata.namespace != 'playground'\"\n\n    # Only process if namespace ends with \"-dev\"\n    #- jmespath: \"ends_with(obj.metadata.namespace,'-dev')\"\n\n#  registries:\n#    dockerio:\n#      username:\n#      password:\n\ntarget:\n  type: aws\n  aws:\n    accountId: 123456789\n    region: ap-southeast-2\n    role: arn:aws:iam::123456789012:role/roleName\n    ecrOptions:\n      tags:\n        - key: CreatedBy\n          value: k8s-image-swapper\n        - key: AnotherTag\n          value: another-tag\n      imageTagMutability: MUTABLE\n      imageScanningConfiguration:\n        imageScanOnPush: true\n      encryptionConfiguration:\n        encryptionType: AES256\n        kmsKey: string\n      accessPolicy: |\n        {\n          \"Version\": \"2008-10-17\",\n          \"Statement\": [\n            {\n              \"Sid\": \"AllowCrossAccountPull\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"AWS\": \"*\"\n              },\n              \"Action\": [\n                \"ecr:GetDownloadUrlForLayer\",\n                \"ecr:BatchGetImage\",\n                \"ecr:BatchCheckLayerAvailability\"\n              ],\n              \"Condition\": {\n                \"StringEquals\": {\n                  \"aws:PrincipalOrgID\": [\n                    \"o-xxxxxxxx\"\n                  ]\n                }\n              }\n            }\n          ]\n        }\n\n      lifecyclePolicy: |\n        {\n          \"rules\": [\n            {\n              \"rulePriority\": 1,\n              \"description\": \"Rule 1\",\n              \"selection\": {\n                \"tagStatus\": \"any\",\n                \"countType\": \"imageCountMoreThan\",\n                \"countNumber\": 1\n              },\n              \"action\": {\n                \"type\": \"expire\"\n              }\n            }\n          ]\n        }\n#    dockerio:\n#    quayio:\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "repos:\n- repo: https://github.com/pre-commit/pre-commit-hooks\n  rev: 38b88246ccc552bffaaf54259d064beeee434539 # v4.0.1\n  hooks:\n  - id: trailing-whitespace\n  - id: check-added-large-files\n  - id: check-json\n  - id: pretty-format-json\n    args: ['--autofix']\n    exclude: package-lock.json\n  - id: check-merge-conflict\n  - id: check-symlinks\n  - id: check-yaml\n    exclude: 'hack/charts/.*\\.yaml'\n  - id: detect-private-key\n  - id: check-merge-conflict\n  - id: check-executables-have-shebangs\n  - id: end-of-file-fixer\n  - id: mixed-line-ending\n#- repo: https://github.com/thlorenz/doctoc\n#  rev: v2.0.0\n#  hooks:\n#  - id: doctoc\n#    args: ['--title', '## Table of Contents']\n- repo:  https://github.com/golangci/golangci-lint\n  rev: v1.55.2\n  hooks:\n    - id: golangci-lint\n      args: ['--timeout', '5m']\n- repo:  https://github.com/dnephin/pre-commit-golang\n  rev: ac0f6582d2484b3aa90b05d568e70f9f3c1374c7 # v0.0.17\n  hooks:\n    - id: go-mod-tidy\n    - id: go-fmt\n    - id: go-imports\n"
  },
  {
    "path": ".releaserc",
    "content": "---\n#verifyConditions: ['@semantic-release/github']\n#prepare: []\n#publish: ['@semantic-release/github']\n#success: ['@semantic-release/github']\n#fail: ['@semantic-release/github']\nplugins:\n- \"@semantic-release/commit-analyzer\"\n- \"@semantic-release/release-notes-generator\"\n- \"@semantic-release/changelog\"\n- \"@semantic-release/github\"\n- \"@semantic-release/git\"\n- - \"@semantic-release/exec\"\n  - generateNotesCmd: |\n      echo \"${nextRelease.notes}\" > /tmp/release-notes.md\n    verifyReleaseCmd: |\n      echo \"${nextRelease.version}\" > /tmp/next-release-version.txt\n\nbranch: main\nbranches:\n- '+([0-9])?(.{+([0-9]),x}).x'\n- 'main'\n- 'next'\n- 'next-major'\n- {name: 'beta', prerelease: true}\n- {name: 'alpha', prerelease: true}\n\nanalyzeCommits:\n  - path: \"@semantic-release/commit-analyzer\"\n    releaseRules:\n    - type: \"build\"\n      scope: \"deps\"\n      release: \"patch\"\n\ngenerateNotes:\n  - path: \"@semantic-release/release-notes-generator\"\n    preset: \"conventionalcommits\"\n    presetConfig:\n      types:\n        - { type: 'feat', section: ':tada: Features' }\n        - { type: 'feature', section: ':tada: Features' }\n        - { type: 'fix', section: ':bug: Bug Fixes' }\n        - { type: 'perf', section: ':zap: Performance Improvements' }\n        - { type: 'revert', section: ':rewind: Reverts' }\n        - { type: 'docs', section: ':memo: Documentation', hidden: false }\n        - { type: 'style', section: 'Styles', hidden: true }\n        - { type: 'chore', section: 'Miscellaneous Chores', hidden: true }\n        - { type: 'refactor', section: 'Code Refactoring', hidden: true }\n        - { type: 'test', section: ':test_tube: Tests', hidden: true }\n        - { type: 'build', scope: 'deps', section: ':arrow_up: Dependencies' }\n        - { type: 'build', section: ':construction_worker: Build System' }\n        - { type: 'ci', section: 'Continuous Integration', hidden: true }\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "## [1.4.0](https://github.com/estahn/k8s-image-swapper/compare/v1.3.3...v1.4.0) (2023-01-01)\n\n\n### :construction_worker: Build System\n\n* **deps-dev:** Bump @semantic-release/changelog from 6.0.1 to 6.0.2 ([#404](https://github.com/estahn/k8s-image-swapper/issues/404)) ([ee56dbc](https://github.com/estahn/k8s-image-swapper/commit/ee56dbc0e352a191e861d1f77839209e561cfd15)), closes [#276](https://github.com/estahn/k8s-image-swapper/issues/276) [#276](https://github.com/estahn/k8s-image-swapper/issues/276) [#272](https://github.com/estahn/k8s-image-swapper/issues/272) [#275](https://github.com/estahn/k8s-image-swapper/issues/275) [#273](https://github.com/estahn/k8s-image-swapper/issues/273) [#274](https://github.com/estahn/k8s-image-swapper/issues/274) [#271](https://github.com/estahn/k8s-image-swapper/issues/271) [#270](https://github.com/estahn/k8s-image-swapper/issues/270) [#269](https://github.com/estahn/k8s-image-swapper/issues/269) [#268](https://github.com/estahn/k8s-image-swapper/issues/268) [#267](https://github.com/estahn/k8s-image-swapper/issues/267)\n\n\n### :tada: Features\n\n* add custom tags to created ECR repositories ([#191](https://github.com/estahn/k8s-image-swapper/issues/191)) ([9849df2](https://github.com/estahn/k8s-image-swapper/commit/9849df2e6f706ebd48c629b3bb1bf6ddb91faf32))\n\n\n### :memo: Documentation\n\n* fix indentation ([b00c57e](https://github.com/estahn/k8s-image-swapper/commit/b00c57ea13a7827458acaf5d2f8c5833d7dfc19d))\n\n\n### :arrow_up: Dependencies\n\n* **deps:** Bump actions/cache from 3.0.11 to 3.2.1 ([#417](https://github.com/estahn/k8s-image-swapper/issues/417)) ([7e7eb8f](https://github.com/estahn/k8s-image-swapper/commit/7e7eb8f9658fe5e890ec52dc3e30a2f2cf645fe9)), closes [actions/cache#1039](https://github.com/actions/cache/issues/1039) [actions/cache#1023](https://github.com/actions/cache/issues/1023) [actions/cache#959](https://github.com/actions/cache/issues/959) [actions/cache#960](https://github.com/actions/cache/issues/960) [actions/cache#963](https://github.com/actions/cache/issues/963) [actions/cache#961](https://github.com/actions/cache/issues/961) [actions/cache#976](https://github.com/actions/cache/issues/976) [actions/cache#971](https://github.com/actions/cache/issues/971) [actions/cache#979](https://github.com/actions/cache/issues/979) [actions/cache#986](https://github.com/actions/cache/issues/986) [actions/cache#981](https://github.com/actions/cache/issues/981) [actions/cache#997](https://github.com/actions/cache/issues/997) [actions/cache#998](https://github.com/actions/cache/issues/998) [actions/cache#1005](https://github.com/actions/cache/issues/1005) [actions/cache#1007](https://github.com/actions/cache/issues/1007) [actions/cache#1013](https://github.com/actions/cache/issues/1013) [actions/cache#1004](https://github.com/actions/cache/issues/1004) [actions/cache#1014](https://github.com/actions/cache/issues/1014) [actions/cache#1008](https://github.com/actions/cache/issues/1008) [actions/cache#1026](https://github.com/actions/cache/issues/1026) [actions/cache#929](https://github.com/actions/cache/issues/929) [actions/cache#1035](https://github.com/actions/cache/issues/1035) [actions/cache#959](https://github.com/actions/cache/issues/959) [actions/cache#979](https://github.com/actions/cache/issues/979) [actions/cache#1013](https://github.com/actions/cache/issues/1013) [actions/cache#1026](https://github.com/actions/cache/issues/1026) [actions/cache#929](https://github.com/actions/cache/issues/929) [actions/cache#1006](https://github.com/actions/cache/issues/1006) [#1023](https://github.com/estahn/k8s-image-swapper/issues/1023) [#1039](https://github.com/estahn/k8s-image-swapper/issues/1039) [#1035](https://github.com/estahn/k8s-image-swapper/issues/1035) [#929](https://github.com/estahn/k8s-image-swapper/issues/929) [#1026](https://github.com/estahn/k8s-image-swapper/issues/1026) [#1008](https://github.com/estahn/k8s-image-swapper/issues/1008) [#1014](https://github.com/estahn/k8s-image-swapper/issues/1014) [#1004](https://github.com/estahn/k8s-image-swapper/issues/1004)\n* **deps:** Bump actions/setup-python from 4.3.0 to 4.3.1 ([#406](https://github.com/estahn/k8s-image-swapper/issues/406)) ([16da762](https://github.com/estahn/k8s-image-swapper/commit/16da762a8223a7802e931a404b236dc18b19cc33)), closes [actions/setup-python#559](https://github.com/actions/setup-python/issues/559) [actions/setup-python#511](https://github.com/actions/setup-python/issues/511) [actions/setup-python#558](https://github.com/actions/setup-python/issues/558) [#559](https://github.com/estahn/k8s-image-swapper/issues/559) [#558](https://github.com/estahn/k8s-image-swapper/issues/558) [#549](https://github.com/estahn/k8s-image-swapper/issues/549) [#546](https://github.com/estahn/k8s-image-swapper/issues/546) [#545](https://github.com/estahn/k8s-image-swapper/issues/545) [#535](https://github.com/estahn/k8s-image-swapper/issues/535) [#510](https://github.com/estahn/k8s-image-swapper/issues/510) [#511](https://github.com/estahn/k8s-image-swapper/issues/511) [#520](https://github.com/estahn/k8s-image-swapper/issues/520)\n* **deps:** Bump actions/setup-python from 4.3.1 to 4.4.0 ([#418](https://github.com/estahn/k8s-image-swapper/issues/418)) ([77872f8](https://github.com/estahn/k8s-image-swapper/commit/77872f801aee9b15d1e878a927372dc5bcf49089)), closes [actions/setup-python#566](https://github.com/actions/setup-python/issues/566) [#567](https://github.com/estahn/k8s-image-swapper/issues/567) [#569](https://github.com/estahn/k8s-image-swapper/issues/569) [#566](https://github.com/estahn/k8s-image-swapper/issues/566)\n* **deps:** Bump github.com/aws/aws-sdk-go from 1.44.146 to 1.44.152 ([#403](https://github.com/estahn/k8s-image-swapper/issues/403)) ([9db51fd](https://github.com/estahn/k8s-image-swapper/commit/9db51fd866049c99d1a6d86cfb4f91570a10cad7)), closes [#4652](https://github.com/estahn/k8s-image-swapper/issues/4652) [#4650](https://github.com/estahn/k8s-image-swapper/issues/4650) [#4648](https://github.com/estahn/k8s-image-swapper/issues/4648) [#4647](https://github.com/estahn/k8s-image-swapper/issues/4647) [#4646](https://github.com/estahn/k8s-image-swapper/issues/4646) [#4644](https://github.com/estahn/k8s-image-swapper/issues/4644) [#4639](https://github.com/estahn/k8s-image-swapper/issues/4639)\n* **deps:** Bump github.com/aws/aws-sdk-go from 1.44.152 to 1.44.157 ([#411](https://github.com/estahn/k8s-image-swapper/issues/411)) ([2188432](https://github.com/estahn/k8s-image-swapper/commit/218843218c86f16f85546a037bd976841f220a82)), closes [#4658](https://github.com/estahn/k8s-image-swapper/issues/4658) [#4657](https://github.com/estahn/k8s-image-swapper/issues/4657) [#4656](https://github.com/estahn/k8s-image-swapper/issues/4656) [#4654](https://github.com/estahn/k8s-image-swapper/issues/4654) [#4653](https://github.com/estahn/k8s-image-swapper/issues/4653)\n* **deps:** Bump github.com/aws/aws-sdk-go from 1.44.157 to 1.44.162 ([#415](https://github.com/estahn/k8s-image-swapper/issues/415)) ([f70fcd9](https://github.com/estahn/k8s-image-swapper/commit/f70fcd98419f805c8314070fb73ca9a6122ad7e2)), closes [#4666](https://github.com/estahn/k8s-image-swapper/issues/4666) [#4665](https://github.com/estahn/k8s-image-swapper/issues/4665) [#4663](https://github.com/estahn/k8s-image-swapper/issues/4663) [#4661](https://github.com/estahn/k8s-image-swapper/issues/4661) [#4660](https://github.com/estahn/k8s-image-swapper/issues/4660)\n* **deps:** Bump github.com/aws/aws-sdk-go from 1.44.162 to 1.44.167 ([#419](https://github.com/estahn/k8s-image-swapper/issues/419)) ([f8b91fe](https://github.com/estahn/k8s-image-swapper/commit/f8b91fe6cbca3ff9a69a9c26e156eadbb8092336)), closes [#4671](https://github.com/estahn/k8s-image-swapper/issues/4671) [#4670](https://github.com/estahn/k8s-image-swapper/issues/4670) [#4669](https://github.com/estahn/k8s-image-swapper/issues/4669) [#4668](https://github.com/estahn/k8s-image-swapper/issues/4668) [#4667](https://github.com/estahn/k8s-image-swapper/issues/4667)\n* **deps:** Bump github.com/gruntwork-io/terratest from 0.41.3 to 0.41.4 ([#402](https://github.com/estahn/k8s-image-swapper/issues/402)) ([16dde07](https://github.com/estahn/k8s-image-swapper/commit/16dde0720dabfa966199f8cd4d57e2de65aaeee3)), closes [#1208](https://github.com/estahn/k8s-image-swapper/issues/1208)\n* **deps:** Bump github.com/gruntwork-io/terratest from 0.41.4 to 0.41.6 ([#409](https://github.com/estahn/k8s-image-swapper/issues/409)) ([9fc87df](https://github.com/estahn/k8s-image-swapper/commit/9fc87df1d6cd4f1979ef64346c88e1601e81855e)), closes [#1214](https://github.com/estahn/k8s-image-swapper/issues/1214) [#1198](https://github.com/estahn/k8s-image-swapper/issues/1198)\n* **deps:** Bump github.com/gruntwork-io/terratest from 0.41.6 to 0.41.7 ([#420](https://github.com/estahn/k8s-image-swapper/issues/420)) ([9ab97f2](https://github.com/estahn/k8s-image-swapper/commit/9ab97f2d3e00c1700efeaef20f3de74471d4dc4a)), closes [gruntwork-io/terratest#1217](https://github.com/gruntwork-io/terratest/issues/1217) [#1217](https://github.com/estahn/k8s-image-swapper/issues/1217)\n* **deps:** Bump goreleaser/goreleaser-action from 3.1.0 to 4.1.0 ([#414](https://github.com/estahn/k8s-image-swapper/issues/414)) ([e963ba1](https://github.com/estahn/k8s-image-swapper/commit/e963ba13406d43412962a025aece512846f85530)), closes [goreleaser/goreleaser-action#382](https://github.com/goreleaser/goreleaser-action/issues/382) [goreleaser/goreleaser-action#366](https://github.com/goreleaser/goreleaser-action/issues/366) [goreleaser/goreleaser-action#379](https://github.com/goreleaser/goreleaser-action/issues/379) [goreleaser/goreleaser-action#383](https://github.com/goreleaser/goreleaser-action/issues/383) [goreleaser/goreleaser-action#366](https://github.com/goreleaser/goreleaser-action/issues/366) [goreleaser/goreleaser-action#379](https://github.com/goreleaser/goreleaser-action/issues/379) [goreleaser/goreleaser-action#370](https://github.com/goreleaser/goreleaser-action/issues/370) [#374](https://github.com/estahn/k8s-image-swapper/issues/374) [#372](https://github.com/estahn/k8s-image-swapper/issues/372) [#373](https://github.com/estahn/k8s-image-swapper/issues/373) [#383](https://github.com/estahn/k8s-image-swapper/issues/383) [#366](https://github.com/estahn/k8s-image-swapper/issues/366) [#382](https://github.com/estahn/k8s-image-swapper/issues/382) [#370](https://github.com/estahn/k8s-image-swapper/issues/370)\n* **deps:** Bump k8s.io/api from 0.25.4 to 0.26.0 ([#407](https://github.com/estahn/k8s-image-swapper/issues/407)) ([d13bf5e](https://github.com/estahn/k8s-image-swapper/commit/d13bf5e751ed7638fb418add00ed40f1ceb04f07)), closes [#111023](https://github.com/estahn/k8s-image-swapper/issues/111023) [#113375](https://github.com/estahn/k8s-image-swapper/issues/113375) [#113186](https://github.com/estahn/k8s-image-swapper/issues/113186)\n* **deps:** Bump k8s.io/client-go from 0.25.4 to 0.26.0 ([#410](https://github.com/estahn/k8s-image-swapper/issues/410)) ([bcc56b5](https://github.com/estahn/k8s-image-swapper/commit/bcc56b57ac91c1e8312f4e558283c68684a38678)), closes [#113797](https://github.com/estahn/k8s-image-swapper/issues/113797) [#111023](https://github.com/estahn/k8s-image-swapper/issues/111023) [#113826](https://github.com/estahn/k8s-image-swapper/issues/113826) [#113375](https://github.com/estahn/k8s-image-swapper/issues/113375)\n\n## [1.3.3](https://github.com/estahn/k8s-image-swapper/compare/v1.3.2...v1.3.3) (2022-12-01)\n\n\n### :arrow_up: Dependencies\n\n* **deps:** Bump alpine from 3.16.2 to 3.16.3 ([#388](https://github.com/estahn/k8s-image-swapper/issues/388)) ([ffae497](https://github.com/estahn/k8s-image-swapper/commit/ffae497511dd6fb3adcac748f06684e50474446c))\n* **deps:** Bump alpine from 3.16.3 to 3.17.0 ([#395](https://github.com/estahn/k8s-image-swapper/issues/395)) ([d32255d](https://github.com/estahn/k8s-image-swapper/commit/d32255d0b7f0685e837eb2906868b0b7c73bd022))\n* **deps:** Bump github.com/aws/aws-sdk-go from 1.44.126 to 1.44.136 ([#391](https://github.com/estahn/k8s-image-swapper/issues/391)) ([61a6ae2](https://github.com/estahn/k8s-image-swapper/commit/61a6ae23f015d7ee0c1c4300a3a7e9a76e2acd09)), closes [#4620](https://github.com/estahn/k8s-image-swapper/issues/4620) [#4619](https://github.com/estahn/k8s-image-swapper/issues/4619) [#4617](https://github.com/estahn/k8s-image-swapper/issues/4617) [#4616](https://github.com/estahn/k8s-image-swapper/issues/4616) [#4615](https://github.com/estahn/k8s-image-swapper/issues/4615) [#4614](https://github.com/estahn/k8s-image-swapper/issues/4614) [#4613](https://github.com/estahn/k8s-image-swapper/issues/4613) [#4611](https://github.com/estahn/k8s-image-swapper/issues/4611) [#4608](https://github.com/estahn/k8s-image-swapper/issues/4608) [#4609](https://github.com/estahn/k8s-image-swapper/issues/4609)\n* **deps:** Bump github.com/aws/aws-sdk-go from 1.44.136 to 1.44.146 ([#397](https://github.com/estahn/k8s-image-swapper/issues/397)) ([d4a6136](https://github.com/estahn/k8s-image-swapper/commit/d4a61369b50b03dd56c1025557b3b65169beea7e)), closes [#4638](https://github.com/estahn/k8s-image-swapper/issues/4638) [#4636](https://github.com/estahn/k8s-image-swapper/issues/4636) [#4633](https://github.com/estahn/k8s-image-swapper/issues/4633) [#4632](https://github.com/estahn/k8s-image-swapper/issues/4632) [#4630](https://github.com/estahn/k8s-image-swapper/issues/4630) [#4628](https://github.com/estahn/k8s-image-swapper/issues/4628) [#4627](https://github.com/estahn/k8s-image-swapper/issues/4627) [#4626](https://github.com/estahn/k8s-image-swapper/issues/4626) [#4625](https://github.com/estahn/k8s-image-swapper/issues/4625) [#4624](https://github.com/estahn/k8s-image-swapper/issues/4624)\n* **deps:** Bump github.com/containers/image/v5 from 5.23.0 to 5.23.1 ([#393](https://github.com/estahn/k8s-image-swapper/issues/393)) ([84f4d18](https://github.com/estahn/k8s-image-swapper/commit/84f4d1800f0e0937560963b5bac6ed52ec824182))\n* **deps:** Bump github.com/go-co-op/gocron from 1.17.1 to 1.18.0 ([#390](https://github.com/estahn/k8s-image-swapper/issues/390)) ([1750ee9](https://github.com/estahn/k8s-image-swapper/commit/1750ee9ebe3dd7e5455d8a8490b90bcccd019eb8)), closes [go-co-op/gocron#388](https://github.com/go-co-op/gocron/issues/388) [go-co-op/gocron#389](https://github.com/go-co-op/gocron/issues/389) [go-co-op/gocron#392](https://github.com/go-co-op/gocron/issues/392) [go-co-op/gocron#394](https://github.com/go-co-op/gocron/issues/394) [go-co-op/gocron#393](https://github.com/go-co-op/gocron/issues/393) [go-co-op/gocron#392](https://github.com/go-co-op/gocron/issues/392) [go-co-op/gocron#394](https://github.com/go-co-op/gocron/issues/394) [#393](https://github.com/estahn/k8s-image-swapper/issues/393) [#394](https://github.com/estahn/k8s-image-swapper/issues/394) [#392](https://github.com/estahn/k8s-image-swapper/issues/392) [#389](https://github.com/estahn/k8s-image-swapper/issues/389)\n* **deps:** Bump github.com/gruntwork-io/terratest from 0.40.24 to 0.41.3 ([#398](https://github.com/estahn/k8s-image-swapper/issues/398)) ([ab35b1a](https://github.com/estahn/k8s-image-swapper/commit/ab35b1a31d45e2c3f395a69f8fb1f11aad6485c0)), closes [gruntwork-io/terratest#1203](https://github.com/gruntwork-io/terratest/issues/1203) [gruntwork-io/terratest#1202](https://github.com/gruntwork-io/terratest/issues/1202) [gruntwork-io/terratest#1201](https://github.com/gruntwork-io/terratest/issues/1201) [gruntwork-io/terratest#1199](https://github.com/gruntwork-io/terratest/issues/1199) [gruntwork-io/terratest#1196](https://github.com/gruntwork-io/terratest/issues/1196) [#1202](https://github.com/estahn/k8s-image-swapper/issues/1202) [#1203](https://github.com/estahn/k8s-image-swapper/issues/1203) [#1201](https://github.com/estahn/k8s-image-swapper/issues/1201) [#1199](https://github.com/estahn/k8s-image-swapper/issues/1199) [#1196](https://github.com/estahn/k8s-image-swapper/issues/1196)\n* **deps:** Bump github.com/prometheus/client_golang from 1.13.0 to 1.13.1 ([#387](https://github.com/estahn/k8s-image-swapper/issues/387)) ([b155a16](https://github.com/estahn/k8s-image-swapper/commit/b155a160cf3f90e3bbbaa8c0a639496d5633072f)), closes [#1146](https://github.com/estahn/k8s-image-swapper/issues/1146) [#1148](https://github.com/estahn/k8s-image-swapper/issues/1148) [#1118](https://github.com/estahn/k8s-image-swapper/issues/1118) [#1146](https://github.com/estahn/k8s-image-swapper/issues/1146) [#1148](https://github.com/estahn/k8s-image-swapper/issues/1148) [#1118](https://github.com/estahn/k8s-image-swapper/issues/1118) [#1157](https://github.com/estahn/k8s-image-swapper/issues/1157) [#1146](https://github.com/estahn/k8s-image-swapper/issues/1146) [#1148](https://github.com/estahn/k8s-image-swapper/issues/1148) [#1118](https://github.com/estahn/k8s-image-swapper/issues/1118)\n* **deps:** Bump github.com/prometheus/client_golang from 1.13.1 to 1.14.0 ([#392](https://github.com/estahn/k8s-image-swapper/issues/392)) ([af00594](https://github.com/estahn/k8s-image-swapper/commit/af00594c9494182d1b6803efa44bc8d13ca7bad6)), closes [#1150](https://github.com/estahn/k8s-image-swapper/issues/1150) [#1103](https://github.com/estahn/k8s-image-swapper/issues/1103) [prometheus/client_golang#1118](https://github.com/prometheus/client_golang/issues/1118) [prometheus/client_golang#1103](https://github.com/prometheus/client_golang/issues/1103) [prometheus/client_golang#1125](https://github.com/prometheus/client_golang/issues/1125) [prometheus/client_golang#1130](https://github.com/prometheus/client_golang/issues/1130) [prometheus/client_golang#1148](https://github.com/prometheus/client_golang/issues/1148) [prometheus/client_golang#1146](https://github.com/prometheus/client_golang/issues/1146) [prometheus/client_golang#1152](https://github.com/prometheus/client_golang/issues/1152) [#1150](https://github.com/estahn/k8s-image-swapper/issues/1150) [#1103](https://github.com/estahn/k8s-image-swapper/issues/1103) [#1162](https://github.com/estahn/k8s-image-swapper/issues/1162) [#1161](https://github.com/estahn/k8s-image-swapper/issues/1161) [#1160](https://github.com/estahn/k8s-image-swapper/issues/1160) [#1136](https://github.com/estahn/k8s-image-swapper/issues/1136) [#1133](https://github.com/estahn/k8s-image-swapper/issues/1133) [#1150](https://github.com/estahn/k8s-image-swapper/issues/1150) [#1152](https://github.com/estahn/k8s-image-swapper/issues/1152)\n* **deps:** Bump github.com/spf13/viper from 1.13.0 to 1.14.0 ([#385](https://github.com/estahn/k8s-image-swapper/issues/385)) ([6f79498](https://github.com/estahn/k8s-image-swapper/commit/6f79498631d0382645ab9e1a031f80f130ea55a6)), closes [spf13/viper#1457](https://github.com/spf13/viper/issues/1457) [spf13/viper#1458](https://github.com/spf13/viper/issues/1458) [spf13/viper#1460](https://github.com/spf13/viper/issues/1460) [spf13/viper#1428](https://github.com/spf13/viper/issues/1428) [spf13/viper#1406](https://github.com/spf13/viper/issues/1406) [spf13/viper#1437](https://github.com/spf13/viper/issues/1437) [spf13/viper#1453](https://github.com/spf13/viper/issues/1453) [spf13/viper#1449](https://github.com/spf13/viper/issues/1449) [spf13/viper#1461](https://github.com/spf13/viper/issues/1461)\n* **deps:** Bump golangci/golangci-lint-action from 3.3.0 to 3.3.1 ([#389](https://github.com/estahn/k8s-image-swapper/issues/389)) ([0b50f7b](https://github.com/estahn/k8s-image-swapper/commit/0b50f7b725bd20f5a2fbe3d7bf74365250de8efd)), closes [golangci/golangci-lint-action#590](https://github.com/golangci/golangci-lint-action/issues/590) [golangci/golangci-lint-action#591](https://github.com/golangci/golangci-lint-action/issues/591) [golangci/golangci-lint-action#592](https://github.com/golangci/golangci-lint-action/issues/592) [golangci/golangci-lint-action#593](https://github.com/golangci/golangci-lint-action/issues/593) [golangci/golangci-lint-action#594](https://github.com/golangci/golangci-lint-action/issues/594) [golangci/golangci-lint-action#595](https://github.com/golangci/golangci-lint-action/issues/595) [golangci/golangci-lint-action#596](https://github.com/golangci/golangci-lint-action/issues/596) [golangci/golangci-lint-action#597](https://github.com/golangci/golangci-lint-action/issues/597) [golangci/golangci-lint-action#598](https://github.com/golangci/golangci-lint-action/issues/598) [golangci/golangci-lint-action#599](https://github.com/golangci/golangci-lint-action/issues/599) [#599](https://github.com/estahn/k8s-image-swapper/issues/599) [#598](https://github.com/estahn/k8s-image-swapper/issues/598) [#596](https://github.com/estahn/k8s-image-swapper/issues/596) [#595](https://github.com/estahn/k8s-image-swapper/issues/595) [#593](https://github.com/estahn/k8s-image-swapper/issues/593) [#591](https://github.com/estahn/k8s-image-swapper/issues/591) [#590](https://github.com/estahn/k8s-image-swapper/issues/590)\n* **deps:** Bump hmarr/auto-approve-action from 2 to 3 ([#396](https://github.com/estahn/k8s-image-swapper/issues/396)) ([0b982a2](https://github.com/estahn/k8s-image-swapper/commit/0b982a220226bad863a4aa4819a080a343c8b238)), closes [hmarr/auto-approve-action#205](https://github.com/hmarr/auto-approve-action/issues/205) [hmarr/auto-approve-action#202](https://github.com/hmarr/auto-approve-action/issues/202) [hmarr/auto-approve-action#202](https://github.com/hmarr/auto-approve-action/issues/202) [hmarr/auto-approve-action#200](https://github.com/hmarr/auto-approve-action/issues/200) [hmarr/auto-approve-action#200](https://github.com/hmarr/auto-approve-action/issues/200) [hmarr/auto-approve-action#186](https://github.com/hmarr/auto-approve-action/issues/186) [hmarr/auto-approve-action#191](https://github.com/hmarr/auto-approve-action/issues/191) [#210](https://github.com/estahn/k8s-image-swapper/issues/210) [#205](https://github.com/estahn/k8s-image-swapper/issues/205)\n* **deps:** Bump k8s.io/api from 0.25.3 to 0.25.4 ([#401](https://github.com/estahn/k8s-image-swapper/issues/401)) ([0f80b5d](https://github.com/estahn/k8s-image-swapper/commit/0f80b5d9802cfe9edf69b69e341b3cff5e22f918))\n* **deps:** Bump k8s.io/apimachinery from 0.25.3 to 0.25.4 ([#399](https://github.com/estahn/k8s-image-swapper/issues/399)) ([1f0944f](https://github.com/estahn/k8s-image-swapper/commit/1f0944ff59bb3803d7e8e379c32838c937ccbed8)), closes [#112218](https://github.com/estahn/k8s-image-swapper/issues/112218) [haoruan/automated-cherry-pick-of-#111936](https://github.com/haoruan/automated-cherry-pick-of-/issues/111936)\n* **deps:** Bump k8s.io/client-go from 0.25.3 to 0.25.4 ([#400](https://github.com/estahn/k8s-image-swapper/issues/400)) ([ad036e0](https://github.com/estahn/k8s-image-swapper/commit/ad036e08999cf58a5c9bd3300d63222c4a1b48e1))\n\n## [1.3.2](https://github.com/estahn/k8s-image-swapper/compare/v1.3.1...v1.3.2) (2022-11-01)\n\n\n### :arrow_up: Dependencies\n\n* **deps:** Bump actions/cache from 3.0.10 to 3.0.11 ([#365](https://github.com/estahn/k8s-image-swapper/issues/365)) ([4e88994](https://github.com/estahn/k8s-image-swapper/commit/4e88994fed8d51b8e21d8c0e5bdeef8edfcf6edb)), closes [actions/cache#946](https://github.com/actions/cache/issues/946) [actions/cache#950](https://github.com/actions/cache/issues/950) [actions/cache#956](https://github.com/actions/cache/issues/956) [actions/cache#950](https://github.com/actions/cache/issues/950) [#956](https://github.com/estahn/k8s-image-swapper/issues/956) [#950](https://github.com/estahn/k8s-image-swapper/issues/950) [#946](https://github.com/estahn/k8s-image-swapper/issues/946)\n* **deps:** bump actions/cache from 3.0.8 to 3.0.10 ([#358](https://github.com/estahn/k8s-image-swapper/issues/358)) ([921d9e2](https://github.com/estahn/k8s-image-swapper/commit/921d9e2df56e860ca2b09c87ed874bce4525260e)), closes [#809](https://github.com/estahn/k8s-image-swapper/issues/809) [#833](https://github.com/estahn/k8s-image-swapper/issues/833) [#810](https://github.com/estahn/k8s-image-swapper/issues/810) [#931](https://github.com/estahn/k8s-image-swapper/issues/931) [#942](https://github.com/estahn/k8s-image-swapper/issues/942) [#930](https://github.com/estahn/k8s-image-swapper/issues/930) [#920](https://github.com/estahn/k8s-image-swapper/issues/920) [#936](https://github.com/estahn/k8s-image-swapper/issues/936) [#925](https://github.com/estahn/k8s-image-swapper/issues/925)\n* **deps:** Bump actions/setup-python from 4.2.0 to 4.3.0 ([#362](https://github.com/estahn/k8s-image-swapper/issues/362)) ([05a7ab3](https://github.com/estahn/k8s-image-swapper/commit/05a7ab3cd2df647927fdd85a159dc21dbe181cc9)), closes [#517](https://github.com/estahn/k8s-image-swapper/issues/517) [#499](https://github.com/estahn/k8s-image-swapper/issues/499) [#443](https://github.com/estahn/k8s-image-swapper/issues/443) [#477](https://github.com/estahn/k8s-image-swapper/issues/477) [#479](https://github.com/estahn/k8s-image-swapper/issues/479) [#491](https://github.com/estahn/k8s-image-swapper/issues/491) [#492](https://github.com/estahn/k8s-image-swapper/issues/492) [#517](https://github.com/estahn/k8s-image-swapper/issues/517) [#503](https://github.com/estahn/k8s-image-swapper/issues/503) [#499](https://github.com/estahn/k8s-image-swapper/issues/499) [#495](https://github.com/estahn/k8s-image-swapper/issues/495) [#443](https://github.com/estahn/k8s-image-swapper/issues/443) [#492](https://github.com/estahn/k8s-image-swapper/issues/492) [#491](https://github.com/estahn/k8s-image-swapper/issues/491)\n* **deps:** Bump docker/login-action from 2.0.0 to 2.1.0 ([#364](https://github.com/estahn/k8s-image-swapper/issues/364)) ([ca6a535](https://github.com/estahn/k8s-image-swapper/commit/ca6a535e78ddc3bba389b1454c4ae6af7c41104b)), closes [#275](https://github.com/estahn/k8s-image-swapper/issues/275) [#252](https://github.com/estahn/k8s-image-swapper/issues/252) [#292](https://github.com/estahn/k8s-image-swapper/issues/292) [#298](https://github.com/estahn/k8s-image-swapper/issues/298) [#299](https://github.com/estahn/k8s-image-swapper/issues/299) [#299](https://github.com/estahn/k8s-image-swapper/issues/299) [#298](https://github.com/estahn/k8s-image-swapper/issues/298) [#292](https://github.com/estahn/k8s-image-swapper/issues/292) [#275](https://github.com/estahn/k8s-image-swapper/issues/275)\n* **deps:** Bump github.com/alitto/pond from 1.8.1 to 1.8.2 ([#371](https://github.com/estahn/k8s-image-swapper/issues/371)) ([417edd5](https://github.com/estahn/k8s-image-swapper/commit/417edd5f35b4194c78f0ff28ba26cb4930202f1f)), closes [alitto/pond#37](https://github.com/alitto/pond/issues/37) [#37](https://github.com/estahn/k8s-image-swapper/issues/37)\n* **deps:** bump github.com/aws/aws-sdk-go from 1.44.100 to 1.44.109 ([#359](https://github.com/estahn/k8s-image-swapper/issues/359)) ([b9acd7d](https://github.com/estahn/k8s-image-swapper/commit/b9acd7d6abaa0d893d084d39655fa6e9c7c6ee03)), closes [#4574](https://github.com/estahn/k8s-image-swapper/issues/4574) [#4573](https://github.com/estahn/k8s-image-swapper/issues/4573) [#4571](https://github.com/estahn/k8s-image-swapper/issues/4571) [#4568](https://github.com/estahn/k8s-image-swapper/issues/4568) [#4567](https://github.com/estahn/k8s-image-swapper/issues/4567) [#4566](https://github.com/estahn/k8s-image-swapper/issues/4566) [#4565](https://github.com/estahn/k8s-image-swapper/issues/4565) [#4562](https://github.com/estahn/k8s-image-swapper/issues/4562) [#4561](https://github.com/estahn/k8s-image-swapper/issues/4561)\n* **deps:** Bump github.com/aws/aws-sdk-go from 1.44.109 to 1.44.114 ([#363](https://github.com/estahn/k8s-image-swapper/issues/363)) ([832a21d](https://github.com/estahn/k8s-image-swapper/commit/832a21d5a4911b73adec3c3922691e64dabeb367)), closes [#4580](https://github.com/estahn/k8s-image-swapper/issues/4580) [#4579](https://github.com/estahn/k8s-image-swapper/issues/4579) [#4578](https://github.com/estahn/k8s-image-swapper/issues/4578) [#4576](https://github.com/estahn/k8s-image-swapper/issues/4576) [#4575](https://github.com/estahn/k8s-image-swapper/issues/4575)\n* **deps:** Bump github.com/aws/aws-sdk-go from 1.44.114 to 1.44.121 ([#377](https://github.com/estahn/k8s-image-swapper/issues/377)) ([26f4103](https://github.com/estahn/k8s-image-swapper/commit/26f410377e56ddd55a3b8f204422492ecb96d45c)), closes [#4596](https://github.com/estahn/k8s-image-swapper/issues/4596) [#4595](https://github.com/estahn/k8s-image-swapper/issues/4595) [#4593](https://github.com/estahn/k8s-image-swapper/issues/4593) [#4519](https://github.com/estahn/k8s-image-swapper/issues/4519) [#4590](https://github.com/estahn/k8s-image-swapper/issues/4590) [#4589](https://github.com/estahn/k8s-image-swapper/issues/4589) [#4587](https://github.com/estahn/k8s-image-swapper/issues/4587) [#4586](https://github.com/estahn/k8s-image-swapper/issues/4586)\n* **deps:** Bump github.com/aws/aws-sdk-go from 1.44.121 to 1.44.126 ([#383](https://github.com/estahn/k8s-image-swapper/issues/383)) ([b7e43d9](https://github.com/estahn/k8s-image-swapper/commit/b7e43d9312709eb9e81e1c941d0915776ca73c10)), closes [#4603](https://github.com/estahn/k8s-image-swapper/issues/4603) [#4602](https://github.com/estahn/k8s-image-swapper/issues/4602) [#4601](https://github.com/estahn/k8s-image-swapper/issues/4601) [#4600](https://github.com/estahn/k8s-image-swapper/issues/4600) [#4598](https://github.com/estahn/k8s-image-swapper/issues/4598)\n* **deps:** bump github.com/containers/image/v5 from 5.22.0 to 5.23.0 ([#360](https://github.com/estahn/k8s-image-swapper/issues/360)) ([250d9e4](https://github.com/estahn/k8s-image-swapper/commit/250d9e4c6c4af0ad80d185e3e28bc72f3bc2a9e2)), closes [#1665](https://github.com/estahn/k8s-image-swapper/issues/1665) [#1666](https://github.com/estahn/k8s-image-swapper/issues/1666) [#1664](https://github.com/estahn/k8s-image-swapper/issues/1664) [#1662](https://github.com/estahn/k8s-image-swapper/issues/1662)\n* **deps:** Bump github.com/dgraph-io/ristretto from 0.1.0 to 0.1.1 ([#368](https://github.com/estahn/k8s-image-swapper/issues/368)) ([0e9c9df](https://github.com/estahn/k8s-image-swapper/commit/0e9c9df74847dea68668137677391541df4a9977)), closes [#285](https://github.com/estahn/k8s-image-swapper/issues/285) [dgraph-io/ristretto#311](https://github.com/dgraph-io/ristretto/issues/311) [#304](https://github.com/estahn/k8s-image-swapper/issues/304) [dgraph-io/ristretto#304](https://github.com/dgraph-io/ristretto/issues/304) [#287](https://github.com/estahn/k8s-image-swapper/issues/287) [dgraph-io/ristretto#307](https://github.com/dgraph-io/ristretto/issues/307) [#285](https://github.com/estahn/k8s-image-swapper/issues/285) [dgraph-io/ristretto#311](https://github.com/dgraph-io/ristretto/issues/311) [#304](https://github.com/estahn/k8s-image-swapper/issues/304) [dgraph-io/ristretto#304](https://github.com/dgraph-io/ristretto/issues/304) [#287](https://github.com/estahn/k8s-image-swapper/issues/287) [dgraph-io/ristretto#307](https://github.com/dgraph-io/ristretto/issues/307) [#312](https://github.com/estahn/k8s-image-swapper/issues/312) [#285](https://github.com/estahn/k8s-image-swapper/issues/285) [#310](https://github.com/estahn/k8s-image-swapper/issues/310) [#306](https://github.com/estahn/k8s-image-swapper/issues/306) [#309](https://github.com/estahn/k8s-image-swapper/issues/309) [#308](https://github.com/estahn/k8s-image-swapper/issues/308) [#287](https://github.com/estahn/k8s-image-swapper/issues/287) [#307](https://github.com/estahn/k8s-image-swapper/issues/307) [#304](https://github.com/estahn/k8s-image-swapper/issues/304)\n* **deps:** Bump github.com/go-co-op/gocron from 1.17.0 to 1.17.1 ([#380](https://github.com/estahn/k8s-image-swapper/issues/380)) ([8c4cef8](https://github.com/estahn/k8s-image-swapper/commit/8c4cef891249bc5e9b699e4df492eb343ee005b6)), closes [go-co-op/gocron#382](https://github.com/go-co-op/gocron/issues/382) [go-co-op/gocron#386](https://github.com/go-co-op/gocron/issues/386) [go-co-op/gocron#386](https://github.com/go-co-op/gocron/issues/386) [#386](https://github.com/estahn/k8s-image-swapper/issues/386) [#382](https://github.com/estahn/k8s-image-swapper/issues/382)\n* **deps:** Bump github.com/gruntwork-io/terratest from 0.40.22 to 0.40.23 ([#367](https://github.com/estahn/k8s-image-swapper/issues/367)) ([a07149b](https://github.com/estahn/k8s-image-swapper/commit/a07149b44a5814d06121de06cf117c8cb1baedc2)), closes [gruntwork-io/terratest#1186](https://github.com/gruntwork-io/terratest/issues/1186) [gruntwork-io/terratest#1189](https://github.com/gruntwork-io/terratest/issues/1189) [#1189](https://github.com/estahn/k8s-image-swapper/issues/1189) [#1186](https://github.com/estahn/k8s-image-swapper/issues/1186)\n* **deps:** Bump github.com/gruntwork-io/terratest from 0.40.23 to 0.40.24 ([#378](https://github.com/estahn/k8s-image-swapper/issues/378)) ([e57939d](https://github.com/estahn/k8s-image-swapper/commit/e57939daf1efa9d3c9e879c5468a7e6451393c69)), closes [gruntwork-io/terratest#1191](https://github.com/gruntwork-io/terratest/issues/1191) [#1191](https://github.com/estahn/k8s-image-swapper/issues/1191)\n* **deps:** Bump github.com/slok/kubewebhook/v2 from 2.3.0 to 2.5.0 ([#372](https://github.com/estahn/k8s-image-swapper/issues/372)) ([249a996](https://github.com/estahn/k8s-image-swapper/commit/249a9961d438d4057e74cca8f5358640a836d521)), closes [#218](https://github.com/estahn/k8s-image-swapper/issues/218) [#217](https://github.com/estahn/k8s-image-swapper/issues/217) [#187](https://github.com/estahn/k8s-image-swapper/issues/187)\n* **deps:** Bump github.com/spf13/cobra from 1.5.0 to 1.6.0 ([#373](https://github.com/estahn/k8s-image-swapper/issues/373)) ([39cfd45](https://github.com/estahn/k8s-image-swapper/commit/39cfd45c188b19fc48ccb06b68542089da2440ca)), closes [#1003](https://github.com/estahn/k8s-image-swapper/issues/1003) [#1802](https://github.com/estahn/k8s-image-swapper/issues/1802) [#1760](https://github.com/estahn/k8s-image-swapper/issues/1760) [#1707](https://github.com/estahn/k8s-image-swapper/issues/1707) [#1813](https://github.com/estahn/k8s-image-swapper/issues/1813) [#1788](https://github.com/estahn/k8s-image-swapper/issues/1788) [#1621](https://github.com/estahn/k8s-image-swapper/issues/1621) [#1467](https://github.com/estahn/k8s-image-swapper/issues/1467) [#1643](https://github.com/estahn/k8s-image-swapper/issues/1643) [#1643](https://github.com/estahn/k8s-image-swapper/issues/1643) [#1762](https://github.com/estahn/k8s-image-swapper/issues/1762) [#1771](https://github.com/estahn/k8s-image-swapper/issues/1771) [#1776](https://github.com/estahn/k8s-image-swapper/issues/1776) [#1766](https://github.com/estahn/k8s-image-swapper/issues/1766) [#1782](https://github.com/estahn/k8s-image-swapper/issues/1782) [#1803](https://github.com/estahn/k8s-image-swapper/issues/1803) [#1783](https://github.com/estahn/k8s-image-swapper/issues/1783) [#1387](https://github.com/estahn/k8s-image-swapper/issues/1387) [#1792](https://github.com/estahn/k8s-image-swapper/issues/1792) [#1744](https://github.com/estahn/k8s-image-swapper/issues/1744) [#1748](https://github.com/estahn/k8s-image-swapper/issues/1748) [#1726](https://github.com/estahn/k8s-image-swapper/issues/1726) [#1656](https://github.com/estahn/k8s-image-swapper/issues/1656) [#1779](https://github.com/estahn/k8s-image-swapper/issues/1779) [#1741](https://github.com/estahn/k8s-image-swapper/issues/1741) [#1742](https://github.com/estahn/k8s-image-swapper/issues/1742) [#1745](https://github.com/estahn/k8s-image-swapper/issues/1745) [#1759](https://github.com/estahn/k8s-image-swapper/issues/1759) [#1772](https://github.com/estahn/k8s-image-swapper/issues/1772) [#1819](https://github.com/estahn/k8s-image-swapper/issues/1819) [#1800](https://github.com/estahn/k8s-image-swapper/issues/1800) [#1809](https://github.com/estahn/k8s-image-swapper/issues/1809) [#1804](https://github.com/estahn/k8s-image-swapper/issues/1804) [#1467](https://github.com/estahn/k8s-image-swapper/issues/1467) [#1003](https://github.com/estahn/k8s-image-swapper/issues/1003) [#1813](https://github.com/estahn/k8s-image-swapper/issues/1813) [#1621](https://github.com/estahn/k8s-image-swapper/issues/1621) [#1792](https://github.com/estahn/k8s-image-swapper/issues/1792) [#1788](https://github.com/estahn/k8s-image-swapper/issues/1788) [#1815](https://github.com/estahn/k8s-image-swapper/issues/1815) [#1819](https://github.com/estahn/k8s-image-swapper/issues/1819) [#1707](https://github.com/estahn/k8s-image-swapper/issues/1707) [#1760](https://github.com/estahn/k8s-image-swapper/issues/1760)\n* **deps:** Bump github.com/spf13/cobra from 1.6.0 to 1.6.1 ([#384](https://github.com/estahn/k8s-image-swapper/issues/384)) ([ffe3ef6](https://github.com/estahn/k8s-image-swapper/commit/ffe3ef6a3ff0f789234b4627ae472047477518dc)), closes [#1839](https://github.com/estahn/k8s-image-swapper/issues/1839) [#1841](https://github.com/estahn/k8s-image-swapper/issues/1841)\n* **deps:** Bump github.com/stretchr/testify from 1.8.0 to 1.8.1 ([#379](https://github.com/estahn/k8s-image-swapper/issues/379)) ([a0e1429](https://github.com/estahn/k8s-image-swapper/commit/a0e1429c3c74ff5c7a69d1949de7f220c04feac0)), closes [#1283](https://github.com/estahn/k8s-image-swapper/issues/1283)\n* **deps:** Bump golangci/golangci-lint-action from 3.2.0 to 3.3.0 ([#375](https://github.com/estahn/k8s-image-swapper/issues/375)) ([abcf765](https://github.com/estahn/k8s-image-swapper/commit/abcf765c74573a98f3c6956c8ce6b087f8a6b8d0)), closes [#586](https://github.com/estahn/k8s-image-swapper/issues/586) [#584](https://github.com/estahn/k8s-image-swapper/issues/584) [#582](https://github.com/estahn/k8s-image-swapper/issues/582) [#580](https://github.com/estahn/k8s-image-swapper/issues/580) [#578](https://github.com/estahn/k8s-image-swapper/issues/578) [#576](https://github.com/estahn/k8s-image-swapper/issues/576) [#577](https://github.com/estahn/k8s-image-swapper/issues/577) [#575](https://github.com/estahn/k8s-image-swapper/issues/575)\n* **deps:** Bump k8s.io/api from 0.25.1 to 0.25.3 ([#366](https://github.com/estahn/k8s-image-swapper/issues/366)) ([c233527](https://github.com/estahn/k8s-image-swapper/commit/c23352795c55ed3d9897015c0d127163648302b8)), closes [#112808](https://github.com/estahn/k8s-image-swapper/issues/112808) [cheftako/automated-cherry-pick-of-#112689](https://github.com/cheftako/automated-cherry-pick-of-/issues/112689)\n\n## [1.3.1](https://github.com/estahn/k8s-image-swapper/compare/v1.3.0...v1.3.1) (2022-10-01)\n\n\n### :bug: Bug Fixes\n\n* set verbose level & use structured logging ([#346](https://github.com/estahn/k8s-image-swapper/issues/346)) ([9b21320](https://github.com/estahn/k8s-image-swapper/commit/9b21320a52d3f74ae4a6e8233cc3e310d2f5136b))\n\n\n### :arrow_up: Dependencies\n\n* **deps:** bump github.com/aws/aws-sdk-go from 1.44.92 to 1.44.95 ([#349](https://github.com/estahn/k8s-image-swapper/issues/349)) ([609e915](https://github.com/estahn/k8s-image-swapper/commit/609e91566628b2c89ee0f3a6f582993cb7df8154)), closes [#4553](https://github.com/estahn/k8s-image-swapper/issues/4553) [#4551](https://github.com/estahn/k8s-image-swapper/issues/4551) [#4550](https://github.com/estahn/k8s-image-swapper/issues/4550)\n* **deps:** bump github.com/aws/aws-sdk-go from 1.44.95 to 1.44.100 ([#351](https://github.com/estahn/k8s-image-swapper/issues/351)) ([c4aba7d](https://github.com/estahn/k8s-image-swapper/commit/c4aba7dd91b6128c4b6b70b52a3587d81a1b439f)), closes [#4560](https://github.com/estahn/k8s-image-swapper/issues/4560) [#4559](https://github.com/estahn/k8s-image-swapper/issues/4559) [#4558](https://github.com/estahn/k8s-image-swapper/issues/4558) [#4556](https://github.com/estahn/k8s-image-swapper/issues/4556) [#4555](https://github.com/estahn/k8s-image-swapper/issues/4555)\n* **deps:** bump github.com/gruntwork-io/terratest from 0.40.21 to 0.40.22 ([#348](https://github.com/estahn/k8s-image-swapper/issues/348)) ([b3fa94d](https://github.com/estahn/k8s-image-swapper/commit/b3fa94df956a05796d8fd396462d0bb6987c8f11)), closes [#1169](https://github.com/estahn/k8s-image-swapper/issues/1169)\n* **deps:** bump k8s.io/api from 0.25.0 to 0.25.1 ([#350](https://github.com/estahn/k8s-image-swapper/issues/350)) ([e1b358a](https://github.com/estahn/k8s-image-swapper/commit/e1b358aa28abacbf4e2c125032871d9db6fab401)), closes [#112161](https://github.com/estahn/k8s-image-swapper/issues/112161) [pohly/automated-cherry-pick-of-#112129](https://github.com/pohly/automated-cherry-pick-of-/issues/112129)\n* **deps:** bump k8s.io/apimachinery from 0.25.0 to 0.25.1 ([#352](https://github.com/estahn/k8s-image-swapper/issues/352)) ([046ad1e](https://github.com/estahn/k8s-image-swapper/commit/046ad1e07924a4b4e797e5984262ef09872e5e50)), closes [#112330](https://github.com/estahn/k8s-image-swapper/issues/112330) [enj/automated-cherry-pick-of-#112193](https://github.com/enj/automated-cherry-pick-of-/issues/112193) [#112161](https://github.com/estahn/k8s-image-swapper/issues/112161) [pohly/automated-cherry-pick-of-#112129](https://github.com/pohly/automated-cherry-pick-of-/issues/112129)\n* **deps:** bump k8s.io/client-go from 0.25.0 to 0.25.1 ([#353](https://github.com/estahn/k8s-image-swapper/issues/353)) ([4525ad4](https://github.com/estahn/k8s-image-swapper/commit/4525ad4a667fda8e86d4c19d3c57f9d2fe9ab7c3)), closes [#112161](https://github.com/estahn/k8s-image-swapper/issues/112161) [pohly/automated-cherry-pick-of-#112129](https://github.com/pohly/automated-cherry-pick-of-/issues/112129) [#112336](https://github.com/estahn/k8s-image-swapper/issues/112336) [enj/automated-cherry-pick-of-#112017](https://github.com/enj/automated-cherry-pick-of-/issues/112017) [#112055](https://github.com/estahn/k8s-image-swapper/issues/112055) [aanm/automated-cherry-pick-of-#111752](https://github.com/aanm/automated-cherry-pick-of-/issues/111752)\n\n## [1.3.0](https://github.com/estahn/k8s-image-swapper/compare/v1.2.3...v1.3.0) (2022-09-07)\n\n\n### :tada: Features\n\n* cross account caching with role ([#336](https://github.com/estahn/k8s-image-swapper/issues/336)) ([98d138e](https://github.com/estahn/k8s-image-swapper/commit/98d138ece9dc27acf20266994e25bef4d43c3d7b))\n\n\n### :arrow_up: Dependencies\n\n* **deps:** bump actions/cache from 3.0.6 to 3.0.8 ([#319](https://github.com/estahn/k8s-image-swapper/issues/319)) ([245ab30](https://github.com/estahn/k8s-image-swapper/commit/245ab30bec7155caaad2ee95689ca71574f69252)), closes [#809](https://github.com/estahn/k8s-image-swapper/issues/809) [#833](https://github.com/estahn/k8s-image-swapper/issues/833) [#810](https://github.com/estahn/k8s-image-swapper/issues/810) [#888](https://github.com/estahn/k8s-image-swapper/issues/888) [#891](https://github.com/estahn/k8s-image-swapper/issues/891) [#899](https://github.com/estahn/k8s-image-swapper/issues/899) [#894](https://github.com/estahn/k8s-image-swapper/issues/894)\n* **deps:** bump alpine from 3.16.1 to 3.16.2 ([da05fdd](https://github.com/estahn/k8s-image-swapper/commit/da05fdd19e9b2540a1a57b30aadabd00ea260f9e))\n* **deps:** bump github.com/alitto/pond from 1.8.0 to 1.8.1 ([#342](https://github.com/estahn/k8s-image-swapper/issues/342)) ([4e50c28](https://github.com/estahn/k8s-image-swapper/commit/4e50c28818fb7db5f2d9b3431a346036109a8f44)), closes [alitto/pond#33](https://github.com/alitto/pond/issues/33) [#34](https://github.com/estahn/k8s-image-swapper/issues/34) [#32](https://github.com/estahn/k8s-image-swapper/issues/32)\n* **deps:** bump github.com/aws/aws-sdk-go from 1.44.70 to 1.44.92 ([0f396c5](https://github.com/estahn/k8s-image-swapper/commit/0f396c57a16e97a5ed01dd310cd7fe808cb0c8b1))\n* **deps:** bump github.com/aws/aws-sdk-go from 1.44.70 to 1.44.92 ([#338](https://github.com/estahn/k8s-image-swapper/issues/338)) ([fa795ae](https://github.com/estahn/k8s-image-swapper/commit/fa795aef3e847fb0f1526dca9efc6cd44ddd9fd9)), closes [#4548](https://github.com/estahn/k8s-image-swapper/issues/4548) [#4546](https://github.com/estahn/k8s-image-swapper/issues/4546) [#4545](https://github.com/estahn/k8s-image-swapper/issues/4545) [#4544](https://github.com/estahn/k8s-image-swapper/issues/4544) [#4543](https://github.com/estahn/k8s-image-swapper/issues/4543) [#4542](https://github.com/estahn/k8s-image-swapper/issues/4542) [#4539](https://github.com/estahn/k8s-image-swapper/issues/4539) [#4536](https://github.com/estahn/k8s-image-swapper/issues/4536) [#4534](https://github.com/estahn/k8s-image-swapper/issues/4534) [#4533](https://github.com/estahn/k8s-image-swapper/issues/4533)\n* **deps:** bump github.com/go-co-op/gocron from 1.16.2 to 1.17.0 ([#340](https://github.com/estahn/k8s-image-swapper/issues/340)) ([645bef3](https://github.com/estahn/k8s-image-swapper/commit/645bef3b6b2ab2c936b0192dd24fd083f64e2034)), closes [go-co-op/gocron#380](https://github.com/go-co-op/gocron/issues/380) [go-co-op/gocron#381](https://github.com/go-co-op/gocron/issues/381) [go-co-op/gocron#375](https://github.com/go-co-op/gocron/issues/375) [#381](https://github.com/estahn/k8s-image-swapper/issues/381) [#380](https://github.com/estahn/k8s-image-swapper/issues/380) [#375](https://github.com/estahn/k8s-image-swapper/issues/375)\n* **deps:** bump github.com/gruntwork-io/terratest from 0.40.19 to 0.40.21 ([#334](https://github.com/estahn/k8s-image-swapper/issues/334)) ([d0f6c39](https://github.com/estahn/k8s-image-swapper/commit/d0f6c39c30c6c47c502b036de3687c73912ecec9)), closes [#1166](https://github.com/estahn/k8s-image-swapper/issues/1166) [#1159](https://github.com/estahn/k8s-image-swapper/issues/1159)\n* **deps:** bump github.com/rs/zerolog from 1.27.0 to 1.28.0 ([#339](https://github.com/estahn/k8s-image-swapper/issues/339)) ([7fb4ff5](https://github.com/estahn/k8s-image-swapper/commit/7fb4ff588ca7f0d177cc9f5bb36066367f9ca84d)), closes [#457](https://github.com/estahn/k8s-image-swapper/issues/457) [#416](https://github.com/estahn/k8s-image-swapper/issues/416) [#454](https://github.com/estahn/k8s-image-swapper/issues/454) [#453](https://github.com/estahn/k8s-image-swapper/issues/453) [#383](https://github.com/estahn/k8s-image-swapper/issues/383) [#396](https://github.com/estahn/k8s-image-swapper/issues/396) [#414](https://github.com/estahn/k8s-image-swapper/issues/414) [#415](https://github.com/estahn/k8s-image-swapper/issues/415) [#430](https://github.com/estahn/k8s-image-swapper/issues/430) [#432](https://github.com/estahn/k8s-image-swapper/issues/432)\n* **deps:** bump github.com/spf13/viper from 1.12.0 to 1.13.0 ([#341](https://github.com/estahn/k8s-image-swapper/issues/341)) ([9b59bd4](https://github.com/estahn/k8s-image-swapper/commit/9b59bd4f308916d207fcfb5c7f3c70eedda1c615)), closes [spf13/viper#1371](https://github.com/spf13/viper/issues/1371) [spf13/viper#1373](https://github.com/spf13/viper/issues/1373) [spf13/viper#1393](https://github.com/spf13/viper/issues/1393) [spf13/viper#1424](https://github.com/spf13/viper/issues/1424) [spf13/viper#1405](https://github.com/spf13/viper/issues/1405) [spf13/viper#1414](https://github.com/spf13/viper/issues/1414) [spf13/viper#1387](https://github.com/spf13/viper/issues/1387) [spf13/viper#1374](https://github.com/spf13/viper/issues/1374) [spf13/viper#1375](https://github.com/spf13/viper/issues/1375) [spf13/viper#1378](https://github.com/spf13/viper/issues/1378) [spf13/viper#1360](https://github.com/spf13/viper/issues/1360) [spf13/viper#1381](https://github.com/spf13/viper/issues/1381) [spf13/viper#1384](https://github.com/spf13/viper/issues/1384) [spf13/viper#1383](https://github.com/spf13/viper/issues/1383) [spf13/viper#1395](https://github.com/spf13/viper/issues/1395) [spf13/viper#1420](https://github.com/spf13/viper/issues/1420) [spf13/viper#1422](https://github.com/spf13/viper/issues/1422) [spf13/viper#1412](https://github.com/spf13/viper/issues/1412) [spf13/viper#1373](https://github.com/spf13/viper/issues/1373) [spf13/viper#1393](https://github.com/spf13/viper/issues/1393) [spf13/viper#1371](https://github.com/spf13/viper/issues/1371) [spf13/viper#1387](https://github.com/spf13/viper/issues/1387) [spf13/viper#1405](https://github.com/spf13/viper/issues/1405) [spf13/viper#1414](https://github.com/spf13/viper/issues/1414)\n* **deps:** bump goreleaser/goreleaser-action from 3.0.0 to 3.1.0 ([#328](https://github.com/estahn/k8s-image-swapper/issues/328)) ([a8d2dd1](https://github.com/estahn/k8s-image-swapper/commit/a8d2dd1916be3b7e686cb2e6814710ab73c5f953)), closes [#369](https://github.com/estahn/k8s-image-swapper/issues/369) [#357](https://github.com/estahn/k8s-image-swapper/issues/357) [#356](https://github.com/estahn/k8s-image-swapper/issues/356) [#360](https://github.com/estahn/k8s-image-swapper/issues/360) [#359](https://github.com/estahn/k8s-image-swapper/issues/359) [#358](https://github.com/estahn/k8s-image-swapper/issues/358) [#367](https://github.com/estahn/k8s-image-swapper/issues/367) [#369](https://github.com/estahn/k8s-image-swapper/issues/369) [#367](https://github.com/estahn/k8s-image-swapper/issues/367) [#358](https://github.com/estahn/k8s-image-swapper/issues/358) [#359](https://github.com/estahn/k8s-image-swapper/issues/359) [#360](https://github.com/estahn/k8s-image-swapper/issues/360) [#357](https://github.com/estahn/k8s-image-swapper/issues/357) [#356](https://github.com/estahn/k8s-image-swapper/issues/356)\n* **deps:** bump k8s.io/api from 0.24.3 to 0.25.0 ([#325](https://github.com/estahn/k8s-image-swapper/issues/325)) ([ce10907](https://github.com/estahn/k8s-image-swapper/commit/ce10907f31431c641269032b823beaff4932f224)), closes [#111657](https://github.com/estahn/k8s-image-swapper/issues/111657) [#109090](https://github.com/estahn/k8s-image-swapper/issues/109090) [#111258](https://github.com/estahn/k8s-image-swapper/issues/111258) [#111113](https://github.com/estahn/k8s-image-swapper/issues/111113) [#111696](https://github.com/estahn/k8s-image-swapper/issues/111696) [#108692](https://github.com/estahn/k8s-image-swapper/issues/108692)\n* **deps:** bump k8s.io/client-go from 0.24.3 to 0.25.0 ([#324](https://github.com/estahn/k8s-image-swapper/issues/324)) ([f7c889f](https://github.com/estahn/k8s-image-swapper/commit/f7c889f4880f0d543c05f70759e8cbfef5c3d7ac))\n\n## [1.2.3](https://github.com/estahn/k8s-image-swapper/compare/v1.2.2...v1.2.3) (2022-09-01)\n\n## [1.2.2](https://github.com/estahn/k8s-image-swapper/compare/v1.2.1...v1.2.2) (2022-08-01)\n\n## [1.2.1](https://github.com/estahn/k8s-image-swapper/compare/v1.2.0...v1.2.1) (2022-07-26)\n\n# [1.2.0](https://github.com/estahn/k8s-image-swapper/compare/v1.1.0...v1.2.0) (2022-07-03)\n\n\n### Bug Fixes\n\n* add missing dash ([228749d](https://github.com/estahn/k8s-image-swapper/commit/228749d98e32a7f90608b37b39d74a108f619f37))\n* bump alpine to 3.16 due to security reports ([f7d6564](https://github.com/estahn/k8s-image-swapper/commit/f7d6564e1d607fa53a44e73f8b495a859c31aac1))\n* docker references with both tag and digest ([5a17075](https://github.com/estahn/k8s-image-swapper/commit/5a170758a58b0244e6001a3aa5911c3be3d076f8)), closes [#48](https://github.com/estahn/k8s-image-swapper/issues/48)\n* failed to solve: executor failed running ([af7df18](https://github.com/estahn/k8s-image-swapper/commit/af7df18a02d6455a4ff8ef1495741ad59cbb4856))\n* setup buildx and qemu for image-scan ([c435048](https://github.com/estahn/k8s-image-swapper/commit/c43504873af1c5fd9c2551f8b77f3220f491ab6a))\n* standard_init_linux.go:228: exec user process caused: exec format error ([b7d0c89](https://github.com/estahn/k8s-image-swapper/commit/b7d0c89d162ed0d71e01620cb074be68b8612ab2))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.40.54 ([7f9dbf5](https://github.com/estahn/k8s-image-swapper/commit/7f9dbf5cf5ddae16e252adc8ce21bb4039cd208d))\n\n\n### Features\n\n* add arm docker build ([be81815](https://github.com/estahn/k8s-image-swapper/commit/be8181590fb899f1515b78fbc02bf02986d72e9c))\n* add full arm support to image copying ([6f14156](https://github.com/estahn/k8s-image-swapper/commit/6f14156acb610541d54d16e85171529de39af6ab))\n\n# [1.1.0](https://github.com/estahn/k8s-image-swapper/compare/v1.0.0...v1.1.0) (2021-10-02)\n\n\n### Bug Fixes\n\n* provide log record for ImageSwapPolicyExists ([179da70](https://github.com/estahn/k8s-image-swapper/commit/179da706fd43c880d71063b786164f9d2cc862e4))\n* timeout for ECR client ([26bdc10](https://github.com/estahn/k8s-image-swapper/commit/26bdc10c3eb21b1dfbea9a659e6b650cb25b335e))\n* **deps:** update module github.com/alitto/pond to v1.5.1 ([504e2dd](https://github.com/estahn/k8s-image-swapper/commit/504e2dde58abf1312dab523cb43073a5cc7bc1b1))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.38.47 ([#70](https://github.com/estahn/k8s-image-swapper/issues/70)) ([4f30053](https://github.com/estahn/k8s-image-swapper/commit/4f300530ac9a6f8250672b272c24168601f42e62))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.40.43 ([266ef01](https://github.com/estahn/k8s-image-swapper/commit/266ef01da6d3caad97dac0f4d0a882dbd75502cc))\n* **deps:** update module github.com/containers/image/v5 to v5.11.0 ([#61](https://github.com/estahn/k8s-image-swapper/issues/61)) ([11d6d28](https://github.com/estahn/k8s-image-swapper/commit/11d6d2843dbaa392a418e2a57fdab27fb5249077))\n* **deps:** update module github.com/containers/image/v5 to v5.16.0 ([5230b91](https://github.com/estahn/k8s-image-swapper/commit/5230b91a7f37e0f4c6d6370d7c1a9231bf13b983))\n* **deps:** update module github.com/dgraph-io/ristretto to v0.1.0 ([#82](https://github.com/estahn/k8s-image-swapper/issues/82)) ([dff1cb1](https://github.com/estahn/k8s-image-swapper/commit/dff1cb186ab1301836f978da1ead02b9ea75bb09))\n* **deps:** update module github.com/go-co-op/gocron to v1.9.0 ([c0e9f11](https://github.com/estahn/k8s-image-swapper/commit/c0e9f111eb6b07d54732cc85464bab06dbfdf5e6))\n* **deps:** update module github.com/rs/zerolog to v1.22.0 ([#76](https://github.com/estahn/k8s-image-swapper/issues/76)) ([c098326](https://github.com/estahn/k8s-image-swapper/commit/c098326273ab31dbd31869c4749164fde7544b67))\n* **deps:** update module github.com/rs/zerolog to v1.23.0 ([#84](https://github.com/estahn/k8s-image-swapper/issues/84)) ([607d5bb](https://github.com/estahn/k8s-image-swapper/commit/607d5bb53a1d7396ae5d504ce49508ceac5e26d6))\n* **deps:** update module github.com/rs/zerolog to v1.25.0 ([72822f4](https://github.com/estahn/k8s-image-swapper/commit/72822f42c762455a1a6932631e36418dc3b92d2a))\n* **deps:** update module github.com/slok/kubewebhook to v2 ([8bd73d4](https://github.com/estahn/k8s-image-swapper/commit/8bd73d47772c0524c552577805d9f01ae365e77f))\n* **deps:** update module github.com/spf13/cobra to v1.2.1 ([ea1e787](https://github.com/estahn/k8s-image-swapper/commit/ea1e7874cdaaa09dea34dd1d4a6f02a7ccb6925c))\n* **deps:** update module github.com/spf13/viper to v1.8.1 ([8a055a2](https://github.com/estahn/k8s-image-swapper/commit/8a055a28343d8dbe780f74f99a275a311549576d))\n* **deps:** update module k8s.io/api to v0.22.1 ([ab6d898](https://github.com/estahn/k8s-image-swapper/commit/ab6d898a2f9faa49b3c4f61f1443eb55bf79d93b))\n* **deps:** update module k8s.io/apimachinery to v0.21.1 ([#79](https://github.com/estahn/k8s-image-swapper/issues/79)) ([aeeeffb](https://github.com/estahn/k8s-image-swapper/commit/aeeeffb4e20c50ecb0e3c0cb46654c3c41f62de0))\n* **deps:** update module k8s.io/apimachinery to v0.22.2 ([ef72c66](https://github.com/estahn/k8s-image-swapper/commit/ef72c665f00d6d1fb454cd596c98b3a72cd7614c))\n\n\n### Features\n\n* Support for imagePullSecrets ([#112](https://github.com/estahn/k8s-image-swapper/issues/112)) ([2d8cf77](https://github.com/estahn/k8s-image-swapper/commit/2d8cf777d32053b8af622cb677d86ac21f526ba8)), closes [#92](https://github.com/estahn/k8s-image-swapper/issues/92) [#19](https://github.com/estahn/k8s-image-swapper/issues/19)\n* Support for pod.spec.initContainers ([#118](https://github.com/estahn/k8s-image-swapper/issues/118)) ([725ff2c](https://github.com/estahn/k8s-image-swapper/commit/725ff2cdc45a13d1a31c3694231482ee09ab2cbd)), closes [#73](https://github.com/estahn/k8s-image-swapper/issues/73) [#96](https://github.com/estahn/k8s-image-swapper/issues/96)\n\n# [1.1.0-alpha.1](https://github.com/estahn/k8s-image-swapper/compare/v1.0.0...v1.1.0-alpha.1) (2021-09-30)\n\n\n### Bug Fixes\n\n* provide log record for ImageSwapPolicyExists ([179da70](https://github.com/estahn/k8s-image-swapper/commit/179da706fd43c880d71063b786164f9d2cc862e4))\n* timeout for ECR client ([26bdc10](https://github.com/estahn/k8s-image-swapper/commit/26bdc10c3eb21b1dfbea9a659e6b650cb25b335e))\n* **deps:** update module github.com/alitto/pond to v1.5.1 ([504e2dd](https://github.com/estahn/k8s-image-swapper/commit/504e2dde58abf1312dab523cb43073a5cc7bc1b1))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.38.47 ([#70](https://github.com/estahn/k8s-image-swapper/issues/70)) ([4f30053](https://github.com/estahn/k8s-image-swapper/commit/4f300530ac9a6f8250672b272c24168601f42e62))\n* **deps:** update module github.com/aws/aws-sdk-go to v1.40.43 ([266ef01](https://github.com/estahn/k8s-image-swapper/commit/266ef01da6d3caad97dac0f4d0a882dbd75502cc))\n* **deps:** update module github.com/containers/image/v5 to v5.11.0 ([#61](https://github.com/estahn/k8s-image-swapper/issues/61)) ([11d6d28](https://github.com/estahn/k8s-image-swapper/commit/11d6d2843dbaa392a418e2a57fdab27fb5249077))\n* **deps:** update module github.com/containers/image/v5 to v5.16.0 ([5230b91](https://github.com/estahn/k8s-image-swapper/commit/5230b91a7f37e0f4c6d6370d7c1a9231bf13b983))\n* **deps:** update module github.com/dgraph-io/ristretto to v0.1.0 ([#82](https://github.com/estahn/k8s-image-swapper/issues/82)) ([dff1cb1](https://github.com/estahn/k8s-image-swapper/commit/dff1cb186ab1301836f978da1ead02b9ea75bb09))\n* **deps:** update module github.com/go-co-op/gocron to v1.9.0 ([c0e9f11](https://github.com/estahn/k8s-image-swapper/commit/c0e9f111eb6b07d54732cc85464bab06dbfdf5e6))\n* **deps:** update module github.com/rs/zerolog to v1.22.0 ([#76](https://github.com/estahn/k8s-image-swapper/issues/76)) ([c098326](https://github.com/estahn/k8s-image-swapper/commit/c098326273ab31dbd31869c4749164fde7544b67))\n* **deps:** update module github.com/rs/zerolog to v1.23.0 ([#84](https://github.com/estahn/k8s-image-swapper/issues/84)) ([607d5bb](https://github.com/estahn/k8s-image-swapper/commit/607d5bb53a1d7396ae5d504ce49508ceac5e26d6))\n* **deps:** update module github.com/rs/zerolog to v1.25.0 ([72822f4](https://github.com/estahn/k8s-image-swapper/commit/72822f42c762455a1a6932631e36418dc3b92d2a))\n* **deps:** update module github.com/slok/kubewebhook to v2 ([8bd73d4](https://github.com/estahn/k8s-image-swapper/commit/8bd73d47772c0524c552577805d9f01ae365e77f))\n* **deps:** update module github.com/spf13/cobra to v1.2.1 ([ea1e787](https://github.com/estahn/k8s-image-swapper/commit/ea1e7874cdaaa09dea34dd1d4a6f02a7ccb6925c))\n* **deps:** update module github.com/spf13/viper to v1.8.1 ([8a055a2](https://github.com/estahn/k8s-image-swapper/commit/8a055a28343d8dbe780f74f99a275a311549576d))\n* **deps:** update module k8s.io/api to v0.22.1 ([ab6d898](https://github.com/estahn/k8s-image-swapper/commit/ab6d898a2f9faa49b3c4f61f1443eb55bf79d93b))\n* **deps:** update module k8s.io/apimachinery to v0.21.1 ([#79](https://github.com/estahn/k8s-image-swapper/issues/79)) ([aeeeffb](https://github.com/estahn/k8s-image-swapper/commit/aeeeffb4e20c50ecb0e3c0cb46654c3c41f62de0))\n* **deps:** update module k8s.io/apimachinery to v0.22.2 ([ef72c66](https://github.com/estahn/k8s-image-swapper/commit/ef72c665f00d6d1fb454cd596c98b3a72cd7614c))\n\n\n### Features\n\n* Support for imagePullSecrets ([#112](https://github.com/estahn/k8s-image-swapper/issues/112)) ([2d8cf77](https://github.com/estahn/k8s-image-swapper/commit/2d8cf777d32053b8af622cb677d86ac21f526ba8)), closes [#92](https://github.com/estahn/k8s-image-swapper/issues/92) [#19](https://github.com/estahn/k8s-image-swapper/issues/19)\n\n# 1.0.0 (2020-12-25)\n\n\n### Bug Fixes\n\n* bump skopeo from 0.2.0 to 1.2.0 ([84025aa](https://github.com/estahn/k8s-image-swapper/commit/84025aaf06d287a306fba98f848e272a19ff8aa0))\n* hardcoded AWS region ([3cc0d49](https://github.com/estahn/k8s-image-swapper/commit/3cc0d492bc17a6ad022cb2794786079759f7bc41)), closes [#20](https://github.com/estahn/k8s-image-swapper/issues/20) [#17](https://github.com/estahn/k8s-image-swapper/issues/17)\n* **chart:** serviceaccount missing annotation tag ([#21](https://github.com/estahn/k8s-image-swapper/issues/21)) ([7164626](https://github.com/estahn/k8s-image-swapper/commit/71646266e54d043f3bba2ee59975e7f9d11f8f13))\n* trace for verbose logs and improve context ([58e05dc](https://github.com/estahn/k8s-image-swapper/commit/58e05dc66644de22183e39dcdc85cf8ce139d8db)), closes [#15](https://github.com/estahn/k8s-image-swapper/issues/15)\n\n\n### Features\n\n* allow filters for container context ([37d0a4d](https://github.com/estahn/k8s-image-swapper/commit/37d0a4d9ac3bd37128c92ede0bff3f4071483b1d)), closes [#32](https://github.com/estahn/k8s-image-swapper/issues/32)\n* automatic token renewal before expiry ([a7c45b8](https://github.com/estahn/k8s-image-swapper/commit/a7c45b8b093efa00e7a04f89a57d5909b4ce068a)), closes [#31](https://github.com/estahn/k8s-image-swapper/issues/31)\n* helm chart ([00f6b74](https://github.com/estahn/k8s-image-swapper/commit/00f6b7409c1f0ab59ea227f5d3b995d532beb623))\n* ImageSwapPolicy defines the mutation strategy used by the webhook. ([9d61659](https://github.com/estahn/k8s-image-swapper/commit/9d616596013d7b1cbb121b0cf137273867bdb19f))\n* POC ([fedcb22](https://github.com/estahn/k8s-image-swapper/commit/fedcb22c2fef26a76bd0fd9dacff70d0d952c077))\n\n# [1.0.0-beta.4](https://github.com/estahn/k8s-image-swapper/compare/v1.0.0-beta.3...v1.0.0-beta.4) (2020-12-23)\n\n\n### Bug Fixes\n\n* bump skopeo from 0.2.0 to 1.2.0 ([09fdb6e](https://github.com/estahn/k8s-image-swapper/commit/09fdb6eb2383c30a45d1a5a7fb3d10a4c6b891e0))\n\n# [1.0.0-beta.3](https://github.com/estahn/k8s-image-swapper/compare/v1.0.0-beta.2...v1.0.0-beta.3) (2020-12-23)\n\n\n### Features\n\n* ImageSwapPolicy defines the mutation strategy used by the webhook. ([e64bc6d](https://github.com/estahn/k8s-image-swapper/commit/e64bc6d120bea925a06cf06f3b22c8184a24fb35))\n\n# [1.0.0-beta.2](https://github.com/estahn/k8s-image-swapper/compare/v1.0.0-beta.1...v1.0.0-beta.2) (2020-12-22)\n\n\n### Features\n\n* allow filters for container context ([c7e4c51](https://github.com/estahn/k8s-image-swapper/commit/c7e4c51a5a04ef9ae8689ffe73ff7d1411f43450)), closes [#32](https://github.com/estahn/k8s-image-swapper/issues/32)\n* automatic token renewal before expiry ([d557c23](https://github.com/estahn/k8s-image-swapper/commit/d557c23e798f4cae61cd412d99f482ec4d310b9f)), closes [#31](https://github.com/estahn/k8s-image-swapper/issues/31)\n\n# 1.0.0-beta.1 (2020-12-21)\n\n\n### Bug Fixes\n\n* hardcoded AWS region ([3cc0d49](https://github.com/estahn/k8s-image-swapper/commit/3cc0d492bc17a6ad022cb2794786079759f7bc41)), closes [#20](https://github.com/estahn/k8s-image-swapper/issues/20) [#17](https://github.com/estahn/k8s-image-swapper/issues/17)\n* **chart:** serviceaccount missing annotation tag ([#21](https://github.com/estahn/k8s-image-swapper/issues/21)) ([7164626](https://github.com/estahn/k8s-image-swapper/commit/71646266e54d043f3bba2ee59975e7f9d11f8f13))\n* trace for verbose logs and improve context ([58e05dc](https://github.com/estahn/k8s-image-swapper/commit/58e05dc66644de22183e39dcdc85cf8ce139d8db)), closes [#15](https://github.com/estahn/k8s-image-swapper/issues/15)\n\n\n### Features\n\n* helm chart ([00f6b74](https://github.com/estahn/k8s-image-swapper/commit/00f6b7409c1f0ab59ea227f5d3b995d532beb623))\n* POC ([fedcb22](https://github.com/estahn/k8s-image-swapper/commit/fedcb22c2fef26a76bd0fd9dacff70d0d952c077))\n\n# 1.0.0-alpha.1 (2020-12-18)\n\n\n### Bug Fixes\n\n* **chart:** serviceaccount missing annotation tag ([#21](https://github.com/estahn/k8s-image-swapper/issues/21)) ([7164626](https://github.com/estahn/k8s-image-swapper/commit/71646266e54d043f3bba2ee59975e7f9d11f8f13))\n* trace for verbose logs and improve context ([58e05dc](https://github.com/estahn/k8s-image-swapper/commit/58e05dc66644de22183e39dcdc85cf8ce139d8db)), closes [#15](https://github.com/estahn/k8s-image-swapper/issues/15)\n\n\n### Features\n\n* helm chart ([00f6b74](https://github.com/estahn/k8s-image-swapper/commit/00f6b7409c1f0ab59ea227f5d3b995d532beb623))\n* POC ([fedcb22](https://github.com/estahn/k8s-image-swapper/commit/fedcb22c2fef26a76bd0fd9dacff70d0d952c077))\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\n[enrico.stahn@gmail.com](mailto:enrico.stahn@gmail.com).\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior,  harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\n[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].\n\nCommunity Impact Guidelines were inspired by\n[Mozilla's code of conduct enforcement ladder][Mozilla CoC].\n\nFor answers to common questions about this code of conduct, see the FAQ at\n[https://www.contributor-covenant.org/faq][FAQ]. Translations are available\nat [https://www.contributor-covenant.org/translations][translations].\n\n[homepage]: https://www.contributor-covenant.org\n[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html\n[Mozilla CoC]: https://github.com/mozilla/diversity\n[FAQ]: https://www.contributor-covenant.org/faq\n[translations]: https://www.contributor-covenant.org/translations\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nBy participating to this project, you agree to abide our\n[code of conduct](/CODE_OF_CONDUCT.md).\n\n## Setup your machine\n\n`k8s-image-swapper` is written in [Go](https://golang.org/).\n\nPrerequisites:\n\n- `make`\n- [Go 1.16+](https://golang.org/doc/install)\n- [golangci-lint](https://golangci-lint.run/usage/install/#local-installation)\n- [Docker](https://www.docker.com/) (or [Podman](https://podman.io/))\n- [kind](https://kind.sigs.k8s.io/)\n- [pre-commit](https://pre-commit.com/) (optional)\n- [ngrok](https://ngrok.com/) (optional)\n\nClone `k8s-image-swapper` anywhere:\n\n```sh\ngit clone git@github.com:estahn/k8s-image-swapper.git\n```\n\nInstall the build and lint dependencies:\n\n```sh\nmake setup\n```\n\nA good way of making sure everything is all right is running the test suite:\n\n```sh\nmake test\n```\n\n## Test your change\n\nYou can create a branch for your changes and try to build from the source as you go:\n\n```sh\nmake test\n```\n\nWhen you are satisfied with the changes, we suggest you run:\n\n```sh\nmake fmt lint test\n```\n\nWhich runs all the linters and tests.\n\n## Create a commit\n\nCommit messages should be well formatted, and to make that \"standardized\", we\nare using Conventional Commits.\n\nYou can follow the documentation on\n[their website](https://www.conventionalcommits.org).\n\n## Submit a pull request\n\nPush your branch to your `k8s-image-swapper` fork and open a pull request against the\nmain branch.\n"
  },
  {
    "path": "Dockerfile",
    "content": "#FROM quay.io/skopeo/stable:v1.2.0 AS skopeo\n#FROM gcr.io/distroless/base-debian10\n#FROM debian:10\n#COPY --from=skopeo /usr/bin/skopeo /skopeo\n\n# TODO: Using alpine for now due to easier installation of skopeo\n#       Will use distroless after incorporating skopeo into the webhook directly\nFROM alpine:3.23.3\nRUN [\"apk\", \"add\", \"--no-cache\", \"--repository=http://dl-cdn.alpinelinux.org/alpine/edge/community\", \"skopeo>=1.2.0\"]\n\nCOPY k8s-image-swapper /\n\nENTRYPOINT [\"/k8s-image-swapper\"]\n\nARG BUILD_DATE\nARG VCS_REF\n\nLABEL maintainer=\"k8s-image-swapper <https://github.com/estahn/k8s-image-swapper/issues>\" \\\n      org.opencontainers.image.title=\"k8s-image-swapper\" \\\n      org.opencontainers.image.description=\"Mirror images into your own registry and swap image references automatically.\" \\\n      org.opencontainers.image.url=\"https://github.com/estahn/k8s-image-swapper\" \\\n      org.opencontainers.image.source=\"https://github.com/estahn/k8s-image-swapper\" \\\n      org.opencontainers.image.vendor=\"estahn\" \\\n      org.label-schema.schema-version=\"1.0\" \\\n      org.label-schema.name=\"k8s-image-swapper\" \\\n      org.label-schema.description=\"Mirror images into your own registry and swap image references automatically.\" \\\n      org.label-schema.url=\"https://github.com/estahn/k8s-image-swapper\" \\\n      org.label-schema.vcs-url=\"git@github.com:estahn/k8s-image-swapper.git\" \\\n      org.label-schema.vendor=\"estahn\" \\\n      org.opencontainers.image.revision=\"$VCS_REF\" \\\n      org.opencontainers.image.created=\"$BUILD_DATE\" \\\n      org.label-schema.vcs-ref=\"$VCS_REF\" \\\n      org.label-schema.build-date=\"$BUILD_DATE\"\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Enrico Stahn\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "SOURCE_FILES?=./...\nTEST_PATTERN?=.\nTEST_OPTIONS?=\n\n.PHONY: help $(MAKECMDGOALS)\n.DEFAULT_GOAL := help\n\nexport GO111MODULE := on\nexport GOPROXY = https://proxy.golang.org,direct\n\nhelp: ## List targets & descriptions\n\t@cat Makefile* | grep -E '^[a-zA-Z_-]+:.*?## .*$$' | sort | awk 'BEGIN {FS = \":.*?## \"}; {printf \"\\033[36m%-30s\\033[0m %s\\n\", $$1, $$2}'\n\nsetup: ## Install dependencies\n\tgo mod download\n\tgo mod tidy\n\ntest: ## Run tests\n\tLC_ALL=C go test $(TEST_OPTIONS) -failfast -race -coverpkg=./... -covermode=atomic -coverprofile=coverage.txt $(SOURCE_FILES) -run $(TEST_PATTERN) -timeout=5m\n\ncover: test ## Run tests and open coverage report\n\tgo tool cover -html=coverage.txt\n\nfmt: ## gofmt and goimports all go files\n\tgofmt -l -w .\n\tgoimports -l -w .\n\nlint: ## Run linters\n\tgolangci-lint run\n\ne2e: ## Run end-to-end tests\n\tgo test -v -run TestHelmDeployment ./test\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <img alt=\"Raiders of the Lost Ark\" src=\"docs/img/indiana.gif\" height=\"140\" />\n  <h3 align=\"center\">k8s-image-swapper</h3>\n  <p align=\"center\">Mirror images into your own registry and swap image references automatically.</p>\n</p>\n\n---\n\n`k8s-image-swapper` is a mutating webhook for Kubernetes, downloading images into your own registry and pointing the images to that new location.\nIt is an alternative to a [docker pull-through proxy](https://docs.docker.com/registry/recipes/mirror/).\n\n**Amazon ECR** and **Google Container Registry** are currently supported.\n\n## :zap: Benefits\n\nUsing `k8s-image-swapper` will improve the overall availability, reliability, durability and resiliency of your\nKubernetes cluster by keeping 3rd-party images mirrored into your own registry.\n\n`k8s-image-swapper` will transparently consolidate all images into a single registry without the need to adjust manifests\ntherefore reducing the impact of external registry failures, rate limiting, network issues, change or removal of images\nwhile reducing data traffic and therefore cost.\n\n**TL;DR:**\n\n* Protect against:\n  * external registry failure ([quay.io outage](https://www.reddit.com/r/devops/comments/f9kiej/quayio_is_experiencing_an_outage/))\n  * image pull rate limiting ([docker.io rate limits](https://www.docker.com/blog/scaling-docker-to-serve-millions-more-developers-network-egress/))\n  * accidental image changes\n  * removal of images\n* Use in air-gaped environments without the need to change manifests\n* Reduce NAT ingress traffic/cost\n\n## :book: Documentation\n\nA comprehensive guide on getting started and a list of configuration options can be found in the documentation.\n\n[![Documentation](https://img.shields.io/badge/Documentation-2FA4E7?style=for-the-badge&logo=ReadMe&logoColor=white)](https://estahn.github.io/k8s-image-swapper/index.html)\n\n## :question: Community\n\nYou have questions, need support and or just want to talk about `k8s-image-swapper`?\n\nHere are ways to get in touch with the community:\n\n[![Slack channel](https://img.shields.io/badge/Slack_Channel-4A154B?style=for-the-badge&logo=slack&logoColor=white)](http://slack.kubernetes.io/)\n[![GitHub Discussions](https://img.shields.io/badge/GITHUB_DISCUSSION-181717?style=for-the-badge&logo=github&logoColor=white)](https://github.com/estahn/k8s-image-swapper/discussions)\n\n\n## :heart_decoration: Sponsor\n\nDoes your company use `k8s-image-swapper`?\nHelp keep the project bug-free and feature rich by [sponsoring the project](https://github.com/sponsors/estahn).\n\n## :office: Commercial Support\n\nDoes your company require individual support or addition of features within a guaranteed timeframe?\nContact me via [email](mailto:enrico.stahn@gmail.com) to discuss.\n\n## :octocat: Badges\n\n[![Release](https://img.shields.io/github/release/estahn/k8s-image-swapper.svg?style=for-the-badge)](https://github.com/estahn/k8s-image-swapper/releases/latest)\n[![Artifact Hub](https://img.shields.io/badge/Artifact_Hub-417598?style=for-the-badge&logo=artifacthub&logoColor=white)](https://artifacthub.io/packages/helm/estahn/k8s-image-swapper)\n[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=for-the-badge)](/LICENSE.md)\n[![Codecov branch](https://img.shields.io/codecov/c/github/estahn/k8s-image-swapper/main.svg?style=for-the-badge)](https://codecov.io/gh/estahn/k8s-image-swapper)\n[![Go Doc](https://img.shields.io/badge/godoc-reference-blue.svg?style=for-the-badge)](http://godoc.org/github.com/estahn/k8s-image-swapper)\n\n## :star2: Stargazers over time\n\n[![Stargazers over time](https://starchart.cc/estahn/k8s-image-swapper.svg)](https://starchart.cc/estahn/k8s-image-swapper)\n"
  },
  {
    "path": "cmd/root.go",
    "content": "/*\nCopyright © 2020 Enrico Stahn <enrico.stahn@gmail.com>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n*/\npackage cmd\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/estahn/k8s-image-swapper/pkg/config\"\n\t\"github.com/estahn/k8s-image-swapper/pkg/registry\"\n\t\"github.com/estahn/k8s-image-swapper/pkg/secrets\"\n\t\"github.com/estahn/k8s-image-swapper/pkg/types\"\n\t\"github.com/estahn/k8s-image-swapper/pkg/webhook\"\n\thomedir \"github.com/mitchellh/go-homedir\"\n\t\"github.com/prometheus/client_golang/prometheus/promhttp\"\n\t\"github.com/rs/zerolog\"\n\t\"github.com/rs/zerolog/log\"\n\tkwhhttp \"github.com/slok/kubewebhook/v2/pkg/http\"\n\t\"github.com/spf13/cobra\"\n\t\"github.com/spf13/viper\"\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/rest\"\n)\n\nvar cfgFile string\nvar cfg *config.Config = &config.Config{}\n\n// rootCmd represents the base command when called without any subcommands\nvar rootCmd = &cobra.Command{\n\tUse:   \"k8s-image-swapper\",\n\tShort: \"Mirror images into your own registry and swap image references automatically.\",\n\tLong: `Mirror images into your own registry and swap image references automatically.\n\nA mutating webhook for Kubernetes, pointing the images to a new location.`,\n\t// Uncomment the following line if your bare application\n\t// has an action associated with it:\n\tRun: func(cmd *cobra.Command, args []string) {\n\t\t//promReg := prometheus.NewRegistry()\n\t\t//metricsRec := metrics.NewPrometheus(promReg)\n\t\tlog.Trace().Interface(\"config\", cfg).Msg(\"config\")\n\n\t\t// Create registry clients for source registries\n\t\tsourceRegistryClients := []registry.Client{}\n\t\tfor _, reg := range cfg.Source.Registries {\n\t\t\tsourceRegistryClient, err := registry.NewClient(reg)\n\t\t\tif err != nil {\n\t\t\t\tlog.Err(err).Msgf(\"error connecting to source registry at %s\", reg.Domain())\n\t\t\t\tos.Exit(1)\n\t\t\t}\n\t\t\tsourceRegistryClients = append(sourceRegistryClients, sourceRegistryClient)\n\t\t}\n\n\t\t// Create a registry client for private target registry\n\t\ttargetRegistryClient, err := registry.NewClient(cfg.Target)\n\t\tif err != nil {\n\t\t\tlog.Err(err).Msgf(\"error connecting to target registry at %s\", cfg.Target.Domain())\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\timageSwapPolicy, err := types.ParseImageSwapPolicy(cfg.ImageSwapPolicy)\n\t\tif err != nil {\n\t\t\tlog.Err(err).Str(\"policy\", cfg.ImageSwapPolicy).Msg(\"parsing image swap policy failed\")\n\t\t}\n\n\t\timageCopyPolicy, err := types.ParseImageCopyPolicy(cfg.ImageCopyPolicy)\n\t\tif err != nil {\n\t\t\tlog.Err(err).Str(\"policy\", cfg.ImageCopyPolicy).Msg(\"parsing image copy policy failed\")\n\t\t}\n\n\t\timageCopyDeadline := config.DefaultImageCopyDeadline\n\t\tif cfg.ImageCopyDeadline != 0 {\n\t\t\timageCopyDeadline = cfg.ImageCopyDeadline\n\t\t}\n\n\t\timagePullSecretProvider := setupImagePullSecretsProvider()\n\n\t\t// Inform secret provider about managed private source registries\n\t\timagePullSecretProvider.SetAuthenticatedRegistries(sourceRegistryClients)\n\n\t\twh, err := webhook.NewImageSwapperWebhookWithOpts(\n\t\t\ttargetRegistryClient,\n\t\t\twebhook.Filters(cfg.Source.Filters),\n\t\t\twebhook.ImagePullSecretsProvider(imagePullSecretProvider),\n\t\t\twebhook.ImageSwapPolicy(imageSwapPolicy),\n\t\t\twebhook.ImageCopyPolicy(imageCopyPolicy),\n\t\t\twebhook.ImageCopyDeadline(imageCopyDeadline),\n\t\t)\n\t\tif err != nil {\n\t\t\tlog.Err(err).Msg(\"error creating webhook\")\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\t// Get the handler for our webhook.\n\t\twhHandler, err := kwhhttp.HandlerFor(kwhhttp.HandlerConfig{Webhook: wh})\n\t\tif err != nil {\n\t\t\tlog.Err(err).Msg(\"error creating webhook handler\")\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\thandler := http.NewServeMux()\n\t\thandler.Handle(\"/webhook\", whHandler)\n\t\thandler.Handle(\"/metrics\", promhttp.Handler())\n\t\thandler.HandleFunc(\"/\", func(w http.ResponseWriter, r *http.Request) {\n\t\t\t_, err := w.Write([]byte(`<html>\n\t\t\t <head><title>k8s-image-webhook</title></head>\n\t\t\t <body>\n\t\t\t <h1>k8s-image-webhook</h1>\n\t\t\t <ul><li><a href='/metrics'>Metrics</a></li><li><a href='/webhook'>Webhook</a></li></ul>\n\t\t\t </body>\n\t\t\t </html>`))\n\n\t\t\tif err != nil {\n\t\t\t\tlog.Error()\n\t\t\t}\n\t\t})\n\n\t\tsrv := &http.Server{\n\t\t\tAddr: cfg.ListenAddress,\n\t\t\t// Good practice to set timeouts to avoid Slowloris attacks.\n\t\t\tWriteTimeout: time.Second * 15,\n\t\t\tReadTimeout:  time.Second * 15,\n\t\t\tIdleTimeout:  time.Second * 60,\n\t\t\tHandler:      handler,\n\t\t}\n\n\t\tgo func() {\n\t\t\tlog.Info().Msgf(\"Listening on %v\", cfg.ListenAddress)\n\t\t\t//err = http.ListenAndServeTLS(\":8080\", cfg.certFile, cfg.keyFile, whHandler)\n\t\t\tif cfg.TLSCertFile != \"\" && cfg.TLSKeyFile != \"\" {\n\t\t\t\tif err := srv.ListenAndServeTLS(cfg.TLSCertFile, cfg.TLSKeyFile); err != nil {\n\t\t\t\t\tlog.Err(err).Msg(\"error serving webhook\")\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif err := srv.ListenAndServe(); err != nil {\n\t\t\t\t\tlog.Err(err).Msg(\"error serving webhook\")\n\t\t\t\t\tos.Exit(1)\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\n\t\tc := make(chan os.Signal, 1)\n\t\t// We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C) or SIGTERM\n\t\t// SIGKILL, SIGQUIT will not be caught.\n\t\tsignal.Notify(c, os.Interrupt, syscall.SIGTERM)\n\n\t\t// Block until we receive our signal.\n\t\t<-c\n\n\t\t// Create a deadline to wait for.\n\t\tvar wait time.Duration\n\t\tctx, cancel := context.WithTimeout(context.Background(), wait)\n\t\tdefer cancel()\n\t\t// Doesn't block if no connections, but will otherwise wait\n\t\t// until the timeout deadline.\n\t\tif err := srv.Shutdown(ctx); err != nil {\n\t\t\tlog.Err(err).Msg(\"Error during shutdown\")\n\t\t}\n\t\t// Optionally, you could run srv.Shutdown in a goroutine and block on\n\t\t// <-ctx.Done() if your application should wait for other services\n\t\t// to finalize based on context cancellation.\n\t\tlog.Info().Msg(\"Shutting down\")\n\t\tos.Exit(0)\n\t},\n}\n\n// Execute adds all child commands to the root command and sets flags appropriately.\n// This is called by main.main(). It only needs to happen once to the rootCmd.\nfunc Execute() {\n\tif err := rootCmd.Execute(); err != nil {\n\t\tfmt.Println(err)\n\t\tos.Exit(1)\n\t}\n}\n\nfunc init() {\n\tcobra.OnInitialize(initConfig, initLogger)\n\n\t// Here you will define your flags and configuration settings.\n\t// Cobra supports persistent flags, which, if defined here,\n\t// will be global for your application.\n\n\trootCmd.PersistentFlags().StringVar(&cfgFile, \"config\", \"\", \"config file (default is $HOME/.k8s-image-swapper.yaml)\")\n\trootCmd.PersistentFlags().StringVar(&cfg.LogLevel, \"log-level\", \"info\", \"Only log messages with the given severity or above. Valid levels: [debug, info, warn, error, fatal]\")\n\trootCmd.PersistentFlags().StringVar(&cfg.LogFormat, \"log-format\", \"json\", \"Format of the log messages. Valid levels: [json, console]\")\n\n\t// Cobra also supports local flags, which will only run\n\t// when this action is called directly.\n\t//rootCmd.Flags().BoolP(\"toggle\", \"t\", false, \"Help message for toggle\")\n\trootCmd.Flags().StringVar(&cfg.ListenAddress, \"listen-address\", \":8443\", \"Address on which to expose the webhook\")\n\trootCmd.Flags().StringVar(&cfg.TLSCertFile, \"tls-cert-file\", \"\", \"File containing the TLS certificate\")\n\trootCmd.Flags().StringVar(&cfg.TLSKeyFile, \"tls-key-file\", \"\", \"File containing the TLS private key\")\n\trootCmd.Flags().BoolVar(&cfg.DryRun, \"dry-run\", true, \"If true, print the action taken without taking it\")\n}\n\n// initConfig reads in config file and ENV variables if set.\nfunc initConfig() {\n\t// Default to aws target registry type if none are defined\n\tconfig.SetViperDefaults(viper.GetViper())\n\n\tif cfgFile != \"\" {\n\t\t// Use config file from the flag.\n\t\tviper.SetConfigFile(cfgFile)\n\t} else {\n\t\t// Find home directory.\n\t\thome, err := homedir.Dir()\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t\tos.Exit(1)\n\t\t}\n\n\t\t// Search config in home directory with name \".k8s-image-swapper\" (without extension).\n\t\tviper.AddConfigPath(home)\n\t\tviper.AddConfigPath(\".\")\n\t\tviper.SetConfigType(\"yaml\")\n\t\tviper.SetConfigName(\".k8s-image-swapper\")\n\t}\n\n\tviper.AutomaticEnv() // read in environment variables that match\n\n\t// If a config file is found, read it in.\n\tif err := viper.ReadInConfig(); err == nil {\n\t\tlog.Info().Str(\"file\", viper.ConfigFileUsed()).Msg(\"using config file\")\n\t}\n\n\tif err := viper.Unmarshal(&cfg); err != nil {\n\t\tlog.Err(err).Msg(\"failed to unmarshal the config file\")\n\t}\n\n\t//validate := validator.New()\n\t//if err := validate.Struct(cfg); err != nil {\n\t//\tvalidationErrors := err.(validator.ValidationErrors)\n\t//\tlog.Err(validationErrors).Msg(\"validation errors for config file\")\n\t//}\n}\n\n// initLogger configures the log level\nfunc initLogger() {\n\tif cfg.LogFormat == \"console\" {\n\t\tlog.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})\n\t}\n\n\tlvl, err := zerolog.ParseLevel(cfg.LogLevel)\n\tif err != nil {\n\t\tlvl = zerolog.InfoLevel\n\t\tlog.Err(err).Msgf(\"could not set log level to '%v'.\", cfg.LogLevel)\n\t}\n\n\tzerolog.SetGlobalLevel(lvl)\n\n\t// add file and line number to log if level is trace\n\tif lvl == zerolog.TraceLevel {\n\t\tlog.Logger = log.With().Caller().Logger()\n\t}\n}\n\n// setupImagePullSecretsProvider configures the provider handling secrets\nfunc setupImagePullSecretsProvider() secrets.ImagePullSecretsProvider {\n\tconfig, err := rest.InClusterConfig()\n\tif err != nil {\n\t\tlog.Warn().Err(err).Msg(\"failed to configure Kubernetes client, will continue without reading secrets\")\n\t\treturn secrets.NewDummyImagePullSecretsProvider()\n\t}\n\n\tclientset, err := kubernetes.NewForConfig(config)\n\tif err != nil {\n\t\tlog.Warn().Err(err).Msg(\"failed to configure Kubernetes client, will continue without reading secrets\")\n\t\treturn secrets.NewDummyImagePullSecretsProvider()\n\t}\n\n\treturn secrets.NewKubernetesImagePullSecretsProvider(clientset)\n}\n"
  },
  {
    "path": "docs/configuration.md",
    "content": "# Configuration\n\nThe configuration is managed via the config file `.k8s-image-swapper.yaml`.\nSome options can be overridden via parameters, e.g. `--dry-run`.\n\n## Dry Run\n\nThe option `dryRun` allows to run the webhook without executing the actions, e.g. repository creation,\nimage download and manifest mutation.\n\n!!! example\n    ```yaml\n    dryRun: true\n    ```\n\n## Log Level & Format\n\nThe option `logLevel` & `logFormat` allow to adjust the verbosity and format (e.g. `json`, `console`).\n\n!!! example\n    ```yaml\n    logLevel: debug\n    logFormat: console\n    ```\n\n## ImageSwapPolicy\n\nThe option `imageSwapPolicy` (default: `exists`) defines the mutation strategy used.\n\n* `always`: Will always swap the image regardless of the image existence in the target registry.\n            This can result in pods ending in state ImagePullBack if images fail to be copied to the target registry.\n* `exists`: Only swaps the image if it exits in the target registry.\n            This can result in pods pulling images from the source registry, e.g. the first pod pulls\n            from source registry, subsequent pods pull from target registry.\n\n## ImageCopyPolicy\n\nThe option `imageCopyPolicy` (default: `delayed`) defines the image copy strategy used.\n\n* `delayed`: Submits the copy job to a process queue and moves on.\n* `immediate`: Submits the copy job to a process queue and waits for it to finish (deadline defined by `imageCopyDeadline`).\n* `force`: Attempts to immediately copy the image (deadline defined by `imageCopyDeadline`).\n* `none`: Do not copy the image.\n\n## ImageCopyDeadline\n\nThe option `imageCopyDeadline` (default: `8s`) defines the duration after which the image copy if aborted.\n\nThis option only applies for `immediate` and `force` image copy strategies.\n\n\n## Source\n\nThis section configures details about the image source.\n\n### Registries\n\nThe option `source.registries` describes a list of registries to pull images from, using a specific configuration.\n\n#### AWS\n\nBy providing configuration on AWS registries you can ask `k8s-image-swapper` to handle the authentication using the same credentials as for the target AWS registry.\nThis authentication method is the default way to get authorized by a private registry if the targeted Pod does not provide an `imagePullSecret`.\n\nRegistries are described with an AWS account ID and region, mostly to construct the ECR domain `[ACCOUNT_ID].dkr.ecr.[REGION].amazonaws.com`.\n\n!!! example\n    ```yaml\n    source:\n      registries:\n        - type: aws\n          aws:\n            accountId: 123456789\n            region: ap-southeast-2\n        - type: aws\n          aws:\n            accountId: 234567890\n            region: us-east-1\n    ```\n### Filters\n\nFilters provide control over what pods will be processed.\nBy default, all pods will be processed.\nIf a condition matches, the pod will **NOT** be processed.\n\n[JMESPath](https://jmespath.org/) is used as query language and allows flexible rules for most use-cases.\n\n!!! info\n    The data structure used for JMESPath is as follows:\n\n    === \"Structure\"\n        ```yaml\n        obj:\n          <Object Spec>\n        container:\n          <Container Spec>\n        ```\n\n    === \"Example\"\n        ```yaml\n        obj:\n          metadata:\n            name: static-web\n            labels:\n              role: myrole\n          spec:\n            containers:\n              - name: web\n                image: nginx\n                ports:\n                  - name: web\n                    containerPort: 80\n                    protocol: TCP\n        container:\n          name: web\n          image: nginx\n          ports:\n            - name: web\n              containerPort: 80\n              protocol: TCP\n        ```\n\nBelow you will find a list of common queries and/or ideas:\n\n!!! tip \"List of common queries/ideas\"\n    * Do not process if namespace equals `kube-system` (_Helm chart default_)\n      ```yaml\n      source:\n        filters:\n          - jmespath: \"obj.metadata.namespace == 'kube-system'\"\n      ```\n    *  Only process if namespace equals `playground`\n       ```yaml\n       source:\n         filters:\n           - jmespath: \"obj.metadata.namespace != 'playground'\"\n       ```\n    * Only process if namespace ends with `-dev`\n      ```yaml\n      source:\n        filters:\n          - jmespath: \"ends_with(obj.metadata.namespace,'-dev')\"\n      ```\n    * Do not process AWS ECR images\n      ```yaml\n      source:\n        filters:\n          - jmespath: \"contains(container.image, '.dkr.ecr.') && contains(container.image, '.amazonaws.com')\"\n      ```\n\n`k8s-image-swapper` will log the filter data and result in `debug` mode.\nThis can be used in conjunction with [JMESPath.org](https://jmespath.org/) which\nhas a live editor that can be used as a playground to experiment with more complex queries.\n\n## Target\n\nThis section configures details about the image target.\nThe option `target` allows to specify which type of registry you set as your target (AWS, GCP...).\nAt the moment, `aws` and `gcp` are the only supported values.\n\n### AWS\n\nThe option `target.aws` holds details about the target registry storing the images.\nThe AWS Account ID and Region is primarily used to construct the ECR domain `[ACCOUNTID].dkr.ecr.[REGION].amazonaws.com`.\n\n!!! example\n    ```yaml\n    target:\n      type: aws\n      aws:\n        accountId: 123456789\n        region: ap-southeast-2\n        prefix: /cache\n    ```\n\n#### ECR Options\n\n##### Tags\n\nThis provides a way to add custom tags to newly created repositories. This may be useful while looking at AWS costs.\nIt's a slice of `Key` and `Value`.\n\n!!! example\n    ```yaml\n    target:\n      type: aws\n      aws:\n        ecrOptions:\n          tags:\n            - key: cluster\n              value: myCluster\n    ```\n\n### GCP\n\nThe option `target.gcp` holds details about the target registry storing the images.\nThe GCP location, projectId, and repositoryId are used to constrct the GCP Artifact Registry domain `[LOCATION]-docker.pkg.dev/[PROJECT_ID]/[REPOSITORY_ID]`.\n\n!!! example\n    ```yaml\n    target:\n      type: gcp\n      gcp:\n        location: us-central1\n        projectId: gcp-project-123\n        repositoryId: main\n    ```\n"
  },
  {
    "path": "docs/faq.md",
    "content": "# FAQ\n\n### Is pulling from private registries supported?\n\nYes, `imagePullSecrets` on `Pod` and `ServiceAccount` level in the hooked pod definition are supported.\n\nIt is also possible to provide a list of ECRs to which authentication is handled by `k8s-image-swapper` using the same credentials as for the target registry. Please see [Configuration > Source - AWS](configuration.md#Private-registries).\n\n### Are config changes reloaded gracefully?\n\nNot yet, they require a pod rotation.\n\n### What happens if the image is not found in the target registry?\n\nPlease see [Configuration > ImageCopyPolicy](configuration.md#imagecopypolicy).\n\n### What level of registry outage does this handle?\n\nIf the source image registry is not reachable it will replace the reference with the target registry reference.\nIf the target registry is down it will do the same. It has no notion of the target registry being up or down.\n\n### What happens if `k8s-image-swapper` is unavailable?\n\nKubernetes will continue to work as if `k8s-image-swapper` was not installed.\nThe webhook [failure policy](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#failure-policy)\nis set to `Ignore`.\n\n!!! tip\n    Environments with strict compliance requirements (or air-gapped) may overwrite this with `Fail` to\n    avoid falling back to the public images.\n\n### Why are sidecar images not being replaced?\n\nA Kubernetes cluster can have multiple mutating webhooks.\nMutating webhooks execute sequentiatlly and each can change a submitted object.\nChanges may be applied after `k8s-image-swapper` was executed, e.g. Istio injecting a sidecar.\n\n```\n... -> k8s-image-swapper -> Istio sidecar injection --> ...\n```\n\nKubernetes 1.15+ allows to re-run webhooks if a mutating webhook modifies an object.\nThe behaviour is controlled by the [Reinvocation policy](https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/#reinvocation-policy).\n\n> reinvocationPolicy may be set to `Never` or `IfNeeded`. It defaults to Never.\n>\n> * `Never`: the webhook must not be called more than once in a single admission evaluation\n> * `IfNeeded`: the webhook may be called again as part of the admission evaluation if the object being admitted is modified by other admission plugins after the initial webhook call.\n\nThe reinvocation policy can be set in the helm chart as follows:\n\n!!! example \"Helm Chart\"\n    ```yaml\n    webhook:\n      reinvocationPolicy: IfNeeded\n    ```\n"
  },
  {
    "path": "docs/getting-started.md",
    "content": "# Getting started\n\nThis document will provide guidance for installing `k8s-image-swapper`.\n\n## Prerequisites\n\n`k8s-image-swapper` will automatically create image repositories and mirror images into them.\nThis requires certain permissions for your target registry (_only AWS ECR and GCP ArtifactRegistry are supported atm_).\n\nBefore you get started choose a namespace to install `k8s-image-swapper` in, e.g. `operations` or `k8s-image-swapper`.\nEnsure the namespace exists and is configured as your current context[^1].\nAll examples below will omit the namespace.\n\n### AWS ECR as a target registry\n\nAWS supports a variety of authentication strategies.\n`k8s-image-swapper` uses the official Amazon AWS SDK and therefore supports [all available authentication strategies](https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html).\nChoose from one of the strategies below or an alternative if needed.\n\n#### IAM credentials\n\n1. Create an IAM user (e.g. `k8s-image-swapper`) with permissions[^2] to create ECR repositories and upload container images.\n   An IAM policy example can be found in the footnotes[^2].\n2. Create a Kubernetes secret (e.g. `k8s-image-swapper-aws`) containing the IAM credentials you just obtained, e.g.\n\n    ```bash\n    kubectl create secret generic k8s-image-swapper-aws \\\n      --from-literal=aws_access_key_id=<...> \\\n      --from-literal=aws_secret_access_key=<...>\n    ```\n\n#### Using ECR registries cross-account\n\nAlthough ECR allows creating registry policy that allows reposistories creation from different account, there's no way to push anything to these repositories.\nECR resource-level policy can not be applied during creation, and to apply it afterwards we need ecr:SetRepositoryPolicy permission, which foreign account doesn't have.\n\nOne way out of this conundrum is to assume the role in target account\n\n```yaml title=\".k8s-image-swapper.yml\"\ntarget:\n  type: aws\n  aws:\n    accountId: 123456789\n    region: ap-southeast-2\n    role: arn:aws:iam::123456789012:role/roleName\n```\n!!! note\n    Make sure that target role has proper trust permissions that allow to assume it cross-account\n\n!!! note\n    In order te be able to pull images from outside accounts, you will have to apply proper access policy\n\n\n#### Access policy\n\nYou can specify the access policy that will be applied to the created repos in config. Policy should be raw json string.\nFor example:\n```yaml title=\".k8s-image-swapper.yml\"\ntarget:\n  type: aws\n  aws:\n    accountId: 123456789\n    region: ap-southeast-2\n    role: arn:aws:iam::123456789012:role/roleName\n    ecrOptions:\n      accessPolicy: |\n        {\n          \"Statement\": [\n            {\n              \"Sid\": \"AllowCrossAccountPull\",\n              \"Effect\": \"Allow\",\n              \"Principal\": {\n                \"AWS\": \"*\"\n              },\n              \"Action\": [\n                \"ecr:GetDownloadUrlForLayer\",\n                \"ecr:BatchGetImage\",\n                \"ecr:BatchCheckLayerAvailability\"\n              ],\n              \"Condition\": {\n                \"StringEquals\": {\n                  \"aws:PrincipalOrgID\": \"o-xxxxxxxxxx\"\n                }\n              }\n            }\n          ],\n          \"Version\": \"2008-10-17\"\n        }\n```\n\n#### Lifecycle policy\n\nSimilarly to access policy, lifecycle policy can be specified, for example:\n\n```yaml title=\".k8s-image-swapper.yml\"\ntarget:\n  type: aws\n  aws:\n    accountId: 123456789\n    region: ap-southeast-2\n    role: arn:aws:iam::123456789012:role/roleName\n    ecrOptions:\n      lifecyclePolicy: |\n        {\n          \"rules\": [\n            {\n              \"rulePriority\": 1,\n              \"description\": \"Rule 1\",\n              \"selection\": {\n                \"tagStatus\": \"any\",\n                \"countType\": \"imageCountMoreThan\",\n                \"countNumber\": 1000\n              },\n              \"action\": {\n                \"type\": \"expire\"\n              }\n            }\n          ]\n        }\n```\n\n#### Service Account\n\n1. Create an Webidentity IAM role (e.g. `k8s-image-swapper`) with the following trust policy, e.g\n```json\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"Federated\": \"arn:aws:iam::${your_aws_account_id}:oidc-provider/${oidc_image_swapper_role_arn}\"\n      },\n      \"Action\": \"sts:AssumeRoleWithWebIdentity\",\n      \"Condition\": {\n        \"StringEquals\": {\n          \"${oidc_image_swapper_role_arn}:sub\": \"system:serviceaccount:${k8s_image_swapper_namespace}:${k8s_image_swapper_serviceaccount_name}\"\n        }\n      }\n    }\n  ]\n}\n```\n\n2. Create and attach permission policy[^2] to the role from Step 1..\n\nNote: You can see a complete example below in [Terraform](Terraform)\n\n### GCP Artifact Registry as a target registry\n\nTo target a GCP Artifact Registry set the `target.type` to `gcp` and provide additional metadata in the configuration.\n\n```yaml title=\".k8s-image-swapper.yml\"\ntarget:\n  type: gcp\n  gcp:\n    location: us-central1\n    projectId: gcp-project-123\n    repositoryId: main\n```\n\n!!! note\n    This is fundamentally different from the AWS ECR implementation since all images will be stored under *one* GCP Artifact Registry repository.\n    <figure markdown>\n      ![GCP Console - Artifact Registry](img/gcp_artifact_registry.png){ loading=lazy }\n    </figure>\n\n#### Create Repository\n\nCreate and configure a single GCP Artifact Registry repository to store Docker images for `k8s-image-swapper`.\n\n=== \"Terraform\"\n\n    ```terraform\n    resource \"google_artifact_registry_repository\" \"repo\" {\n      project       = var.project_id\n      location      = var.region\n      repository_id = \"main\"\n      description   = \"main docker repository\"\n      format        = \"DOCKER\"\n    }\n    ```\n\n#### IAM for GKE / Nodes / Compute\n\nGive the compute service account that the nodes use, permissions to pull images from Artifact Registry.\n\n=== \"Terraform\"\n\n    ```terraform\n    resource \"google_project_iam_member\" \"compute_artifactregistry_reader\" {\n      project = var.project_id\n      role    = \"roles/artifactregistry.reader\"\n      member  = \"serviceAccount:${var.compute_sa_email}\"\n    }\n    ```\n\nAllow GKE node pools to access Artifact Registry API via OAuth scope `https://www.googleapis.com/auth/devstorage.read_only`\n\n=== \"Terraform\"\n\n    ```terraform\n    resource \"google_container_node_pool\" \"primary_nodes_v1\" {\n      project  = var.project_id\n      name     = \"${google_container_cluster.primary.name}-node-pool-v1\"\n      location = var.region\n      cluster  = google_container_cluster.primary.name\n      ...\n      node_config {\n        oauth_scopes = [\n          ...\n          \"https://www.googleapis.com/auth/devstorage.read_only\",\n        ]\n        ...\n      }\n      ...\n    }\n    ```\n\n#### IAM for `k8s-image-swapper`\n\nOn GKE, leverage Workload Identity for the `k8s-image-swapper` K8s service account.\n\n1. Enable Workload Identity on the GKE cluster[^3].\n\n    === \"Terraform\"\n\n        ```terraform\n        resource \"google_container_cluster\" \"primary\" {\n          ...\n          workload_identity_config {\n            workload_pool = \"${var.project_id}.svc.id.goog\"\n          }\n          ...\n        }\n        ```\n\n2. Setup a Google Service Account (GSA) for `k8s-image-swapper`.\n\n    === \"Terraform\"\n\n        ```terraform\n        resource \"google_service_account\" \"k8s_image_swapper_service_account\" {\n          project      = var.project_id\n          account_id   = k8s-image-swapper\n          display_name = \"Workload identity for kube-system/k8s-image-swapper\"\n        }\n        ```\n\n3. Setup Workload Identity for the GSA\n\n    !!! note\n        This example assumes `k8s-image-swapper` is deployed to the `kube-system` namespace and uses `k8s-image-swapper` as the K8s service account name.\n\n    === \"Terraform\"\n\n        ```terraform\n        resource \"google_service_account_iam_member\" \"k8s_image_swapper_workload_identity_binding\" {\n          service_account_id = google_service_account.k8s_image_swapper_service_account.name\n          role               = \"roles/iam.workloadIdentityUser\"\n          member             = \"serviceAccount:${var.project_id}.svc.id.goog[kube-system/k8s-image-swapper]\"\n\n          depends_on = [\n            google_container_cluster.primary,\n          ]\n        }\n        ```\n\n4. Bind permissions for GSA to access Artifact Registry\n\n    Setup the `roles/artifactregistry.writer` role in order for `k8s-image-swapper` to be able to read/write images to the Artifact Repository.\n\n    === \"Terraform\"\n\n        ```terraform\n        resource \"google_project_iam_member\" \"k8s_image_swapper_service_account_binding\" {\n          project  = var.project_id\n          role     = \"roles/artifactregistry.writer\"\n          member   = \"serviceAccount:${google_service_account.k8s_image_swapper_service_account.email}\"\n        }\n        ```\n\n5. (Optional) Bind additional permissions for GSA to read from other GCP Artifact Registries\n6. Set Workload Identity annotation on `k8s-iamge-swapper` service account\n   ```yaml\n   serviceAccount:\n     annotations:\n       iam.gke.io/gcp-service-account: k8s-image-swapper@gcp-project-123.iam.gserviceaccount.com\n   ```\n\n#### Firewall\n\nIf running `k8s-image-swapper` on a private GKE cluster you must have a firewall rule enabled to allow the GKE control plane to talk to `k8s-image-swapper` on port `8443`. See the following Terraform example for the firewall configuration.\n\n=== \"Terraform\"\n\n    ```terraform\n    resource \"google_compute_firewall\" \"k8s_image_swapper_webhook\" {\n      project       = var.project_id\n      name          = \"gke-${google_container_cluster.primary.name}-k8s-image-swapper-webhook\"\n      network       = google_compute_network.vpc.name\n      direction     = \"INGRESS\"\n      source_ranges = [google_container_cluster.primary.private_cluster_config[0].master_ipv4_cidr_block]\n      target_tags   = [google_container_cluster.primary.name]\n\n      allow {\n        ports    = [\"8443\"]\n        protocol = \"tcp\"\n      }\n    }\n    ```\n\nFor more details see https://cloud.google.com/kubernetes-engine/docs/how-to/private-clusters#add_firewall_rules\n\n## Helm\n\n1. Add the Helm chart repository:\n   ```bash\n   helm repo add estahn https://estahn.github.io/charts/\n   ```\n2. Update the local chart information:\n   ```bash\n   helm repo update\n   ```\n3. Install `k8s-image-swapper`\n   ```\n   helm install k8s-image-swapper estahn/k8s-image-swapper \\\n     --set config.target.aws.accountId=$AWS_ACCOUNT_ID \\\n     --set config.target.aws.region=$AWS_DEFAULT_REGION \\\n     --set awsSecretName=k8s-image-swapper-aws\n   ```\n\n!!! note\n    `awsSecretName` is not required for the Service Account method and instead the service account is annotated:\n    ```yaml\n    serviceAccount:\n      create: true\n      annotations:\n        eks.amazonaws.com/role-arn: ${oidc_image_swapper_role_arn}\n    ```\n\n## Terraform\n\nFull example of helm chart deployment with AWS service account setup in Terraform.\n\n\n```terraform\ndata \"aws_caller_identity\" \"current\" {\n}\n\nvariable \"cluster_oidc_provider\" {\n  default = \"oidc.eks.ap-southeast-1.amazonaws.com/id/ABCDEFGHIJKLMNOPQRSTUVWXYZ012345\"\n  description = \"example oidc endpoint that is created during eks deployment\"\n}\n\nvariable  \"cluster_name\" {\n  default = \"test\"\n  description = \"name of the eks cluster being deployed to\"\n}\n\n\nvariable  \"region\" {\n  default = \"ap-southeast-1\"\n  description = \"name of the eks cluster being deployed to\"\n}\n\nvariable \"k8s_image_swapper_namespace\" {\n  default     = \"kube-system\"\n  description = \"namespace to install k8s-image-swapper\"\n}\n\nvariable \"k8s_image_swapper_name\" {\n  default     = \"k8s-image-swapper\"\n  description = \"name for k8s-image-swapper release and service account\"\n}\n\n#k8s-image-swapper helm chart\nresource \"helm_release\" \"k8s_image_swapper\" {\n  name       = var.k8s_image_swapper_name\n  namespace  = \"kube-system\"\n  repository = \"https://estahn.github.io/charts/\"\n  chart   = \"k8s-image-swapper\"\n  keyring = \"\"\n  version = \"1.0.1\"\n  values = [\n    <<YAML\nconfig:\n  dryRun: true\n  logLevel: debug\n  logFormat: console\n\n  source:\n    # Filters provide control over what pods will be processed.\n    # By default all pods will be processed. If a condition matches, the pod will NOT be processed.\n    # For query language details see https://jmespath.org/\n    filters:\n      - jmespath: \"obj.metadata.namespace != 'default'\"\n      - jmespath: \"contains(container.image, '.dkr.ecr.') && contains(container.image, '.amazonaws.com')\"\n  target:\n    type: aws\n    aws:\n      accountId: \"${data.aws_caller_identity.current.account_id}\"\n      region: ${var.region}\n\nsecretReader:\n  enabled: true\n\nserviceAccount:\n  # Specifies whether a service account should be created\n  create: true\n  # Specifies annotations for this service account\n  annotations:\n    eks.amazonaws.com/role-arn: \"arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/${aws_iam_role.k8s_image_swapper.name}\"\nYAML\n    ,\n  ]\n}\n\n#iam policy for k8s-image-swapper service account\nresource \"aws_iam_role_policy\" \"k8s_image_swapper\" {\n  name = \"${var.cluster_name}-${var.k8s_image_swapper_name}\"\n  role = aws_iam_role.k8s_image_swapper.id\n\n  policy = <<-EOF\n{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Sid\": \"\",\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"ecr:GetAuthorizationToken\",\n                \"ecr:DescribeRepositories\",\n                \"ecr:DescribeRegistry\"\n            ],\n            \"Resource\": \"*\"\n        },\n        {\n            \"Sid\": \"\",\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"ecr:BatchCheckLayerAvailability\",\n                \"ecr:BatchGetImage\",\n                \"ecr:CompleteLayerUpload\",\n                \"ecr:CreateRepository\",\n                \"ecr:GetDownloadUrlForLayer\",\n                \"ecr:InitiateLayerUpload\",\n                \"ecr:ListImages\",\n                \"ecr:PutImage\",\n                \"ecr:PutLifecyclePolicy\",\n                \"ecr:UploadLayerPart\"\n            ],\n            \"Resource\": [\n              \"arn:aws:ecr:*:${data.aws_caller_identity.current.account_id}:repository/docker.io/*\",\n              \"arn:aws:ecr:*:${data.aws_caller_identity.current.account_id}:repository/quay.io/*\"\n\t    ]\n        }\n    ]\n}\nEOF\n}\n\n#role for k8s-image-swapper service account\nresource \"aws_iam_role\" \"k8s_image_swapper\" {\n  name               = \"${var.cluster_name}-${var.k8s_image_swapper_name}\"\n  assume_role_policy = <<-EOF\n{\n  \"Version\": \"2012-10-17\",\n  \"Statement\": [\n    {\n      \"Effect\": \"Allow\",\n      \"Principal\": {\n        \"Federated\": \"arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/${replace(var.cluster_oidc_provider, \"/https:///\", \"\")}\"\n      },\n      \"Action\": \"sts:AssumeRoleWithWebIdentity\",\n      \"Condition\": {\n        \"StringEquals\": {\n          \"${replace(var.cluster_oidc_provider, \"/https:///\", \"\")}:sub\": \"system:serviceaccount:${var.k8s_image_swapper_namespace}:${var.k8s_image_swapper_name}\"\n        }\n      }\n    }\n  ]\n}\nEOF\n}\n\n```\n\n[^1]: Use a tool like [kubectx & kubens](https://github.com/ahmetb/kubectx) for convienience.\n[^2]:\n    ??? tldr \"IAM Policy\"\n        ```json\n        {\n            \"Version\": \"2012-10-17\",\n            \"Statement\": [\n                {\n                    \"Sid\": \"\",\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"ecr:GetAuthorizationToken\",\n                        \"ecr:DescribeRepositories\",\n                        \"ecr:DescribeRegistry\",\n                        \"ecr:TagResource\"\n                    ],\n                    \"Resource\": \"*\"\n                },\n                {\n                    \"Sid\": \"\",\n                    \"Effect\": \"Allow\",\n                    \"Action\": [\n                        \"ecr:BatchCheckLayerAvailability\"\n                        \"ecr:BatchGetImage\",\n                        \"ecr:CompleteLayerUpload\",\n                        \"ecr:CreateRepository\",\n                        \"ecr:GetDownloadUrlForLayer\",\n                        \"ecr:InitiateLayerUpload\",\n                        \"ecr:ListImages\",\n                        \"ecr:PutImage\",\n                        \"ecr:PutLifecyclePolicy\",\n                        \"ecr:UploadLayerPart\"\n                    ],\n                    \"Resource\": \"arn:aws:ecr:*:123456789:repository/*\"\n                }\n            ]\n        }\n        ```\n\n        !!! tip \"Further restricting access\"\n            The resource configuration allows access to all AWS ECR repositories within the account 123456789.\n            Restrict this further by repository name or tag.\n            `k8s-image-swapper` will create repositories with the source registry as prefix, e.g. `nginx` --> `docker.io/library/nginx:latest`.\n\n[^3]: [Google Kubernetes Engine (GKE) > Documentation > Guides > Use Workload Identity](https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity)\n[^4]: [Google Kubernetes Engine (GKE) > Documentation > Guides > Creating a private cluster > Adding firewall rules for specific use cases](https://cloud.google.com/kubernetes-engine/docs/how-to/private-clusters#add_firewall_rules)\n"
  },
  {
    "path": "docs/index.md",
    "content": "<p align=\"center\">\n  <img alt=\"Raiders of the Lost Ark\" src=\"img/indiana.gif\" height=\"140\" />\n  <h3 align=\"center\">k8s-image-swapper</h3>\n  <p align=\"center\">Mirror images into your own registry and swap image references automatically.</p>\n</p>\n\n`k8s-image-swapper` is a mutating webhook for Kubernetes, downloading images into your own registry and pointing the images to that new location.\nIt is an alternative to a [docker pull-through proxy](https://docs.docker.com/registry/recipes/mirror/).\nThe feature set was primarily designed with Amazon ECR in mind but may work with other registries.\n\n## Benefits\n\nUsing `k8s-image-swapper` will improve the overall availability, reliability, durability and resiliency of your\nKubernetes cluster by keeping 3rd-party images mirrored into your own registry.\n\n`k8s-image-swapper` will transparently consolidate all images into a single registry without the need to adjust manifests\ntherefore reducing the impact of external registry failures, rate limiting, network issues, change or removal of images\nwhile reducing data traffic and therefore cost.\n\n**TL;DR:**\n\n* Protect against:\n    * external registry failure ([quay.io outage](https://www.reddit.com/r/devops/comments/f9kiej/quayio_is_experiencing_an_outage/))\n    * image pull rate limiting ([docker.io rate limits](https://www.docker.com/blog/scaling-docker-to-serve-millions-more-developers-network-egress/))\n    * accidental image changes\n    * removal of images\n* Use in air-gaped environments without the need to change manifests\n* Reduce NAT ingress traffic/cost\n\n## How it works\n\n![Explainer](img/k8s-image-swapper_explainer.gif)\n"
  },
  {
    "path": "docs/overrides/main.html",
    "content": "{% extends \"base.html\" %}\n\n{% block outdated %}\n  You're not viewing the latest version.\n  <a href=\"{{ '../' ~ base_url }}\">\n    <strong>Click here to go to latest.</strong>\n  </a>\n{% endblock %}\n\n{% block extrahead %}\n<meta name=\"google-site-verification\" content=\"WZHhaHX7z1JWk0UfBwO6xUK3X_dlGq7TZd9sFBG5xQ8\" />\n{% endblock %}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/estahn/k8s-image-swapper\n\ngo 1.24.0\n\nrequire (\n\tcloud.google.com/go/artifactregistry v1.17.1\n\tgithub.com/alitto/pond v1.9.2\n\tgithub.com/aws/aws-sdk-go v1.55.7\n\tgithub.com/containers/image/v5 v5.36.2\n\tgithub.com/dgraph-io/ristretto v0.2.0\n\tgithub.com/evanphx/json-patch v5.9.11+incompatible\n\tgithub.com/go-co-op/gocron v1.37.0\n\tgithub.com/gruntwork-io/terratest v0.50.0\n\tgithub.com/jmespath/go-jmespath v0.4.0\n\tgithub.com/mitchellh/go-homedir v1.1.0\n\tgithub.com/prometheus/client_golang v1.23.2\n\tgithub.com/rs/zerolog v1.34.0\n\tgithub.com/slok/kubewebhook/v2 v2.5.0\n\tgithub.com/spf13/cobra v1.10.1\n\tgithub.com/spf13/viper v1.21.0\n\tgithub.com/stretchr/testify v1.11.1\n\tgoogle.golang.org/api v0.250.0\n\tgopkg.in/yaml.v2 v2.4.0 // indirect\n\tk8s.io/api v0.33.4\n\tk8s.io/apimachinery v0.33.4\n\tk8s.io/client-go v0.33.4\n\tsigs.k8s.io/yaml v1.4.0 // indirect\n)\n\nrequire (\n\tcloud.google.com/go v0.120.0 // indirect\n\tcloud.google.com/go/auth v0.16.5 // indirect\n\tcloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect\n\tcloud.google.com/go/compute/metadata v0.9.0 // indirect\n\tcloud.google.com/go/iam v1.5.2 // indirect\n\tcloud.google.com/go/longrunning v0.6.7 // indirect\n\tdario.cat/mergo v1.0.2 // indirect\n\tfilippo.io/edwards25519 v1.1.1 // indirect\n\tgithub.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect\n\tgithub.com/BurntSushi/toml v1.5.0 // indirect\n\tgithub.com/Microsoft/go-winio v0.6.2 // indirect\n\tgithub.com/Microsoft/hcsshim v0.13.0 // indirect\n\tgithub.com/agext/levenshtein v1.2.3 // indirect\n\tgithub.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect\n\tgithub.com/aws/aws-sdk-go-v2 v1.41.5 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/config v1.28.5 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/credentials v1.17.46 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.41 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/acm v1.30.6 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/autoscaling v1.51.0 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.65.0 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/dynamodb v1.37.1 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/ec2 v1.193.0 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/ecr v1.36.6 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/ecs v1.52.0 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/iam v1.38.1 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.5 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/kms v1.37.6 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/lambda v1.88.5 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/rds v1.91.0 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/route53 v1.46.2 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.6 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sns v1.33.6 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sqs v1.37.1 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/ssm v1.56.0 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sso v1.24.6 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 // indirect\n\tgithub.com/aws/aws-sdk-go-v2/service/sts v1.33.1 // indirect\n\tgithub.com/aws/smithy-go v1.24.2 // indirect\n\tgithub.com/beorn7/perks v1.0.1 // indirect\n\tgithub.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect\n\tgithub.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect\n\tgithub.com/cespare/xxhash/v2 v2.3.0 // indirect\n\tgithub.com/containerd/cgroups/v3 v3.0.5 // indirect\n\tgithub.com/containerd/errdefs v1.0.0 // indirect\n\tgithub.com/containerd/errdefs/pkg v0.3.0 // indirect\n\tgithub.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect\n\tgithub.com/containerd/typeurl/v2 v2.2.3 // indirect\n\tgithub.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 // indirect\n\tgithub.com/containers/ocicrypt v1.2.1 // indirect\n\tgithub.com/containers/storage v1.59.1 // indirect\n\tgithub.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect\n\tgithub.com/cyphar/filepath-securejoin v0.4.1 // indirect\n\tgithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect\n\tgithub.com/distribution/reference v0.6.0 // indirect\n\tgithub.com/docker/distribution v2.8.3+incompatible // indirect\n\tgithub.com/docker/docker v28.3.3+incompatible // indirect\n\tgithub.com/docker/docker-credential-helpers v0.9.3 // indirect\n\tgithub.com/docker/go-connections v0.5.0 // indirect\n\tgithub.com/docker/go-units v0.5.0 // indirect\n\tgithub.com/dustin/go-humanize v1.0.1 // indirect\n\tgithub.com/emicklei/go-restful/v3 v3.11.0 // indirect\n\tgithub.com/felixge/httpsnoop v1.0.4 // indirect\n\tgithub.com/fsnotify/fsnotify v1.9.0 // indirect\n\tgithub.com/fxamacker/cbor/v2 v2.7.0 // indirect\n\tgithub.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0 // indirect\n\tgithub.com/go-logr/logr v1.4.3 // indirect\n\tgithub.com/go-logr/stdr v1.2.2 // indirect\n\tgithub.com/go-openapi/jsonpointer v0.21.0 // indirect\n\tgithub.com/go-openapi/jsonreference v0.21.0 // indirect\n\tgithub.com/go-openapi/swag v0.23.1 // indirect\n\tgithub.com/go-sql-driver/mysql v1.8.1 // indirect\n\tgithub.com/go-viper/mapstructure/v2 v2.4.0 // indirect\n\tgithub.com/gogo/protobuf v1.3.2 // indirect\n\tgithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect\n\tgithub.com/gonvenience/bunt v1.3.5 // indirect\n\tgithub.com/gonvenience/neat v1.3.12 // indirect\n\tgithub.com/gonvenience/term v1.0.2 // indirect\n\tgithub.com/gonvenience/text v1.0.7 // indirect\n\tgithub.com/gonvenience/wrap v1.1.2 // indirect\n\tgithub.com/gonvenience/ytbx v1.4.4 // indirect\n\tgithub.com/google/gnostic-models v0.6.9 // indirect\n\tgithub.com/google/go-cmp v0.7.0 // indirect\n\tgithub.com/google/go-containerregistry v0.20.3 // indirect\n\tgithub.com/google/go-intervals v0.0.2 // indirect\n\tgithub.com/google/s2a-go v0.1.9 // indirect\n\tgithub.com/google/uuid v1.6.0 // indirect\n\tgithub.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect\n\tgithub.com/googleapis/gax-go/v2 v2.15.0 // indirect\n\tgithub.com/gorilla/mux v1.8.1 // indirect\n\tgithub.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect\n\tgithub.com/gruntwork-io/go-commons v0.8.0 // indirect\n\tgithub.com/hashicorp/errwrap v1.1.0 // indirect\n\tgithub.com/hashicorp/go-cleanhttp v0.5.2 // indirect\n\tgithub.com/hashicorp/go-getter/v2 v2.2.3 // indirect\n\tgithub.com/hashicorp/go-multierror v1.1.1 // indirect\n\tgithub.com/hashicorp/go-safetemp v1.0.0 // indirect\n\tgithub.com/hashicorp/go-version v1.7.0 // indirect\n\tgithub.com/hashicorp/hcl/v2 v2.22.0 // indirect\n\tgithub.com/hashicorp/terraform-json v0.23.0 // indirect\n\tgithub.com/homeport/dyff v1.6.0 // indirect\n\tgithub.com/inconshreveable/mousetrap v1.1.0 // indirect\n\tgithub.com/jackc/pgpassfile v1.0.0 // indirect\n\tgithub.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect\n\tgithub.com/jackc/pgx/v5 v5.7.1 // indirect\n\tgithub.com/jackc/puddle/v2 v2.2.2 // indirect\n\tgithub.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a // indirect\n\tgithub.com/josharian/intern v1.0.0 // indirect\n\tgithub.com/json-iterator/go v1.1.12 // indirect\n\tgithub.com/klauspost/compress v1.18.0 // indirect\n\tgithub.com/klauspost/pgzip v1.2.6 // indirect\n\tgithub.com/lucasb-eyer/go-colorful v1.2.0 // indirect\n\tgithub.com/mailru/easyjson v0.9.0 // indirect\n\tgithub.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 // indirect\n\tgithub.com/mattn/go-colorable v0.1.13 // indirect\n\tgithub.com/mattn/go-isatty v0.0.19 // indirect\n\tgithub.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326 // indirect\n\tgithub.com/mistifyio/go-zfs/v3 v3.0.1 // indirect\n\tgithub.com/mitchellh/go-ps v1.0.0 // indirect\n\tgithub.com/mitchellh/go-testing-interface v1.14.1 // indirect\n\tgithub.com/mitchellh/go-wordwrap v1.0.1 // indirect\n\tgithub.com/mitchellh/hashstructure v1.1.0 // indirect\n\tgithub.com/moby/docker-image-spec v1.3.1 // indirect\n\tgithub.com/moby/spdystream v0.5.0 // indirect\n\tgithub.com/moby/sys/capability v0.4.0 // indirect\n\tgithub.com/moby/sys/mountinfo v0.7.2 // indirect\n\tgithub.com/moby/sys/user v0.4.0 // indirect\n\tgithub.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect\n\tgithub.com/modern-go/reflect2 v1.0.2 // indirect\n\tgithub.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect\n\tgithub.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect\n\tgithub.com/opencontainers/go-digest v1.0.0 // indirect\n\tgithub.com/opencontainers/image-spec v1.1.1 // indirect\n\tgithub.com/opencontainers/runtime-spec v1.2.1 // indirect\n\tgithub.com/opencontainers/selinux v1.12.0 // indirect\n\tgithub.com/pelletier/go-toml/v2 v2.2.4 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect\n\tgithub.com/pquerna/otp v1.4.0 // indirect\n\tgithub.com/prometheus/client_model v0.6.2 // indirect\n\tgithub.com/prometheus/common v0.66.1 // indirect\n\tgithub.com/prometheus/procfs v0.16.1 // indirect\n\tgithub.com/robfig/cron/v3 v3.0.1 // indirect\n\tgithub.com/russross/blackfriday/v2 v2.1.0 // indirect\n\tgithub.com/sagikazarmark/locafero v0.11.0 // indirect\n\tgithub.com/sergi/go-diff v1.3.1 // indirect\n\tgithub.com/sirupsen/logrus v1.9.3 // indirect\n\tgithub.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect\n\tgithub.com/spf13/afero v1.15.0 // indirect\n\tgithub.com/spf13/cast v1.10.0 // indirect\n\tgithub.com/spf13/pflag v1.0.10 // indirect\n\tgithub.com/stretchr/objx v0.5.2 // indirect\n\tgithub.com/subosito/gotenv v1.6.0 // indirect\n\tgithub.com/sylabs/sif/v2 v2.21.1 // indirect\n\tgithub.com/tchap/go-patricia/v2 v2.3.3 // indirect\n\tgithub.com/texttheater/golang-levenshtein v1.0.1 // indirect\n\tgithub.com/tmccombs/hcl2json v0.6.4 // indirect\n\tgithub.com/ulikunitz/xz v0.5.14 // indirect\n\tgithub.com/urfave/cli v1.22.16 // indirect\n\tgithub.com/vbatts/tar-split v0.12.1 // indirect\n\tgithub.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 // indirect\n\tgithub.com/x448/float16 v0.8.4 // indirect\n\tgithub.com/zclconf/go-cty v1.15.0 // indirect\n\tgo.opencensus.io v0.24.0 // indirect\n\tgo.opentelemetry.io/auto/sdk v1.2.1 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect\n\tgo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect\n\tgo.opentelemetry.io/otel v1.39.0 // indirect\n\tgo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect\n\tgo.opentelemetry.io/otel/metric v1.39.0 // indirect\n\tgo.opentelemetry.io/otel/trace v1.39.0 // indirect\n\tgo.opentelemetry.io/proto/otlp v1.7.0 // indirect\n\tgo.uber.org/atomic v1.9.0 // indirect\n\tgo.yaml.in/yaml/v2 v2.4.2 // indirect\n\tgo.yaml.in/yaml/v3 v3.0.4 // indirect\n\tgolang.org/x/crypto v0.46.0 // indirect\n\tgolang.org/x/mod v0.30.0 // indirect\n\tgolang.org/x/net v0.48.0 // indirect\n\tgolang.org/x/oauth2 v0.34.0 // indirect\n\tgolang.org/x/sync v0.19.0 // indirect\n\tgolang.org/x/sys v0.39.0 // indirect\n\tgolang.org/x/term v0.38.0 // indirect\n\tgolang.org/x/text v0.32.0 // indirect\n\tgolang.org/x/time v0.13.0 // indirect\n\tgolang.org/x/tools v0.39.0 // indirect\n\tgomodules.xyz/jsonpatch/v3 v3.0.1 // indirect\n\tgomodules.xyz/orderedmap v0.1.0 // indirect\n\tgoogle.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect\n\tgoogle.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect\n\tgoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect\n\tgoogle.golang.org/grpc v1.79.3 // indirect\n\tgoogle.golang.org/protobuf v1.36.10 // indirect\n\tgopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect\n\tgopkg.in/inf.v0 v0.9.1 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n\tk8s.io/klog/v2 v2.130.1 // indirect\n\tk8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect\n\tk8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect\n\tsigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect\n\tsigs.k8s.io/randfill v1.0.0 // indirect\n\tsigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ncloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA=\ncloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q=\ncloud.google.com/go/artifactregistry v1.17.1 h1:A20kj2S2HO9vlyBVyVFHPxArjxkXvLP5LjcdE7NhaPc=\ncloud.google.com/go/artifactregistry v1.17.1/go.mod h1:06gLv5QwQPWtaudI2fWO37gfwwRUHwxm3gA8Fe568Hc=\ncloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI=\ncloud.google.com/go/auth v0.16.5/go.mod h1:utzRfHMP+Vv0mpOkTRQoWD2q3BatTOoWbA7gCc2dUhQ=\ncloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=\ncloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=\ncloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=\ncloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=\ncloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=\ncloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=\ncloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=\ncloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=\ndario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=\ndario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=\nfilippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw=\nfilippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=\ngithub.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=\ngithub.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=\ngithub.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=\ngithub.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=\ngithub.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=\ngithub.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=\ngithub.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA=\ngithub.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok=\ngithub.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=\ngithub.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=\ngithub.com/alitto/pond v1.9.2 h1:9Qb75z/scEZVCoSU+osVmQ0I0JOeLfdTDafrbcJ8CLs=\ngithub.com/alitto/pond v1.9.2/go.mod h1:xQn3P/sHTYcU/1BR3i86IGIrilcrGC2LiS+E2+CJWsI=\ngithub.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=\ngithub.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=\ngithub.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=\ngithub.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=\ngithub.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE=\ngithub.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=\ngithub.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY=\ngithub.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=\ngithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o=\ngithub.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=\ngithub.com/aws/aws-sdk-go-v2/config v1.28.5 h1:Za41twdCXbuyyWv9LndXxZZv3QhTG1DinqlFsSuvtI0=\ngithub.com/aws/aws-sdk-go-v2/config v1.28.5/go.mod h1:4VsPbHP8JdcdUDmbTVgNL/8w9SqOkM5jyY8ljIxLO3o=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.17.46 h1:AU7RcriIo2lXjUfHFnFKYsLCwgbz1E7Mm95ieIRDNUg=\ngithub.com/aws/aws-sdk-go-v2/credentials v1.17.46/go.mod h1:1FmYyLGL08KQXQ6mcTlifyFXfJVCNJTVGuQP4m0d/UA=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 h1:sDSXIrlsFSFJtWKLQS4PUWRvrT580rrnuLydJrCQ/yA=\ngithub.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20/go.mod h1:WZ/c+w0ofps+/OUqMwWgnfrgzZH1DZO1RIkktICsqnY=\ngithub.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.41 h1:hqcxMc2g/MwwnRMod9n6Bd+t+9Nf7d5qRg7RaXKPd6o=\ngithub.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.41/go.mod h1:d1eH0VrttvPmrCraU68LOyNdu26zFxQFjrVSb5vdhog=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4=\ngithub.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A=\ngithub.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=\ngithub.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=\ngithub.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ=\ngithub.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y=\ngithub.com/aws/aws-sdk-go-v2/service/acm v1.30.6 h1:fDg0RlN30Xf/yYzEUL/WXqhmgFsjVb/I3230oCfyI5w=\ngithub.com/aws/aws-sdk-go-v2/service/acm v1.30.6/go.mod h1:zRR6jE3v/TcbfO8C2P+H0Z+kShiKKVaVyoIl8NQRjyg=\ngithub.com/aws/aws-sdk-go-v2/service/autoscaling v1.51.0 h1:1KzQVZi7OTixxaVJ8fWaJAUBjme+iQ3zBOCZhE4RgxQ=\ngithub.com/aws/aws-sdk-go-v2/service/autoscaling v1.51.0/go.mod h1:I1+/2m+IhnK5qEbhS3CrzjeiVloo9sItE/2K+so0fkU=\ngithub.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.65.0 h1:3yaFbUbuLfN8n1q01wZtQtHRzUDc/jm0VvniMY0IPE8=\ngithub.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.65.0/go.mod h1:PobeppEnIjw4pcgjFryNDZCTH7AiqZw0yb5r98Gvf9c=\ngithub.com/aws/aws-sdk-go-v2/service/dynamodb v1.37.1 h1:vucMirlM6D+RDU8ncKaSZ/5dGrXNajozVwpmWNPn2gQ=\ngithub.com/aws/aws-sdk-go-v2/service/dynamodb v1.37.1/go.mod h1:fceORfs010mNxZbQhfqUjUeHlTwANmIT4mvHamuUaUg=\ngithub.com/aws/aws-sdk-go-v2/service/ec2 v1.193.0 h1:RhSoBFT5/8tTmIseJUXM6INTXTQDF8+0oyxWBnozIms=\ngithub.com/aws/aws-sdk-go-v2/service/ec2 v1.193.0/go.mod h1:mzj8EEjIHSN2oZRXiw1Dd+uB4HZTl7hC8nBzX9IZMWw=\ngithub.com/aws/aws-sdk-go-v2/service/ecr v1.36.6 h1:zg+3FGHA0PBs0KM25qE/rOf2o5zsjNa1g/Qq83+SDI0=\ngithub.com/aws/aws-sdk-go-v2/service/ecr v1.36.6/go.mod h1:ZSq54Z9SIsOTf1Efwgw1msilSs4XVEfVQiP9nYVnKpM=\ngithub.com/aws/aws-sdk-go-v2/service/ecs v1.52.0 h1:7/vgFWplkusJN/m+3QOa+W9FNRqa8ujMPNmdufRaJpg=\ngithub.com/aws/aws-sdk-go-v2/service/ecs v1.52.0/go.mod h1:dPTOvmjJQ1T7Q+2+Xs2KSPrMvx+p0rpyV+HsQVnUK4o=\ngithub.com/aws/aws-sdk-go-v2/service/iam v1.38.1 h1:hfkzDZHBp9jAT4zcd5mtqckpU4E3Ax0LQaEWWk1VgN8=\ngithub.com/aws/aws-sdk-go-v2/service/iam v1.38.1/go.mod h1:u36ahDtZcQHGmVm/r+0L1sfKX4fzLEMdCqiKRKkUMVM=\ngithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY=\ngithub.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=\ngithub.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM=\ngithub.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM=\ngithub.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.5 h1:3Y457U2eGukmjYjeHG6kanZpDzJADa2m0ADqnuePYVQ=\ngithub.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.5/go.mod h1:CfwEHGkTjYZpkQ/5PvcbEtT7AJlG68KkEvmtwU8z3/U=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto=\ngithub.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA=\ngithub.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM=\ngithub.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ=\ngithub.com/aws/aws-sdk-go-v2/service/kms v1.37.6 h1:CZImQdb1QbU9sGgJ9IswhVkxAcjkkD1eQTMA1KHWk+E=\ngithub.com/aws/aws-sdk-go-v2/service/kms v1.37.6/go.mod h1:YJDdlK0zsyxVBxGU48AR/Mi8DMrGdc1E3Yij4fNrONA=\ngithub.com/aws/aws-sdk-go-v2/service/lambda v1.88.5 h1:HWN7xwaV7Zwrn3Jlauio4u4aTMFgRzG2fblHWQeir/k=\ngithub.com/aws/aws-sdk-go-v2/service/lambda v1.88.5/go.mod h1:6HBXRyFFqOw+ALkJ6YGHfrr20/YXYv6X9pcZErXRvCA=\ngithub.com/aws/aws-sdk-go-v2/service/rds v1.91.0 h1:eqHz3Uih+gb0vLE5Cc4Xf733vOxsxDp6GFUUVQU4d7w=\ngithub.com/aws/aws-sdk-go-v2/service/rds v1.91.0/go.mod h1:h2jc7IleH3xHY7y+h8FH7WAZcz3IVLOB6/jXotIQ/qU=\ngithub.com/aws/aws-sdk-go-v2/service/route53 v1.46.2 h1:wmt05tPp/CaRZpPV5B4SaJ5TwkHKom07/BzHoLdkY1o=\ngithub.com/aws/aws-sdk-go-v2/service/route53 v1.46.2/go.mod h1:d+K9HESMpGb1EU9/UmmpInbGIUcAkwmcY6ZO/A3zZsw=\ngithub.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 h1:HwxWTbTrIHm5qY+CAEur0s/figc3qwvLWsNkF4RPToo=\ngithub.com/aws/aws-sdk-go-v2/service/s3 v1.97.3/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM=\ngithub.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.6 h1:1KDMKvOKNrpD667ORbZ/+4OgvUoaok1gg/MLzrHF9fw=\ngithub.com/aws/aws-sdk-go-v2/service/secretsmanager v1.34.6/go.mod h1:DmtyfCfONhOyVAJ6ZMTrDSFIeyCBlEO93Qkfhxwbxu0=\ngithub.com/aws/aws-sdk-go-v2/service/sns v1.33.6 h1:lEUtRHICiXsd7VRwRjXaY7MApT2X4Ue0Mrwe6XbyBro=\ngithub.com/aws/aws-sdk-go-v2/service/sns v1.33.6/go.mod h1:SODr0Lu3lFdT0SGsGX1TzFTapwveBrT5wztVoYtppm8=\ngithub.com/aws/aws-sdk-go-v2/service/sqs v1.37.1 h1:39WvSrVq9DD6UHkD+fx5x19P5KpRQfNdtgReDVNbelc=\ngithub.com/aws/aws-sdk-go-v2/service/sqs v1.37.1/go.mod h1:3gwPzC9LER/BTQdQZ3r6dUktb1rSjABF1D3Sr6nS7VU=\ngithub.com/aws/aws-sdk-go-v2/service/ssm v1.56.0 h1:mADKqoZaodipGgiZfuAjtlcr4IVBtXPZKVjkzUZCCYM=\ngithub.com/aws/aws-sdk-go-v2/service/ssm v1.56.0/go.mod h1:l9qF25TzH95FhcIak6e4vt79KE4I7M2Nf59eMUVjj6c=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.24.6 h1:3zu537oLmsPfDMyjnUS2g+F2vITgy5pB74tHI+JBNoM=\ngithub.com/aws/aws-sdk-go-v2/service/sso v1.24.6/go.mod h1:WJSZH2ZvepM6t6jwu4w/Z45Eoi75lPN7DcydSRtJg6Y=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 h1:K0OQAsDywb0ltlFrZm0JHPY3yZp/S9OaoLU33S7vPS8=\ngithub.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5/go.mod h1:ORITg+fyuMoeiQFiVGoqB3OydVTLkClw/ljbblMq6Cc=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.33.1 h1:6SZUVRQNvExYlMLbHdlKB48x0fLbc2iVROyaNEwBHbU=\ngithub.com/aws/aws-sdk-go-v2/service/sts v1.33.1/go.mod h1:GqWyYCwLXnlUB1lOAXQyNSPqPLQJvmo8J0DWBzp9mtg=\ngithub.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=\ngithub.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=\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/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=\ngithub.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=\ngithub.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=\ngithub.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=\ngithub.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=\ngithub.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=\ngithub.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=\ngithub.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 h1:6xNmx7iTtyBRev0+D/Tv1FZd4SCg8axKApyNyRsAt/w=\ngithub.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5/go.mod h1:KdCmV+x/BuvyMxRnYBlmVaq4OLiKW6iRQfvC62cvdkI=\ngithub.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo=\ngithub.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=\ngithub.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=\ngithub.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=\ngithub.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=\ngithub.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=\ngithub.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=\ngithub.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=\ngithub.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8=\ngithub.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU=\ngithub.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40=\ngithub.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk=\ngithub.com/containers/image/v5 v5.36.2 h1:GcxYQyAHRF/pLqR4p4RpvKllnNL8mOBn0eZnqJbfTwk=\ngithub.com/containers/image/v5 v5.36.2/go.mod h1:b4GMKH2z/5t6/09utbse2ZiLK/c72GuGLFdp7K69eA4=\ngithub.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01 h1:Qzk5C6cYglewc+UyGf6lc8Mj2UaPTHy/iF2De0/77CA=\ngithub.com/containers/libtrust v0.0.0-20230121012942-c1716e8a8d01/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY=\ngithub.com/containers/ocicrypt v1.2.1 h1:0qIOTT9DoYwcKmxSt8QJt+VzMY18onl9jUXsxpVhSmM=\ngithub.com/containers/ocicrypt v1.2.1/go.mod h1:aD0AAqfMp0MtwqWgHM1bUwe1anx0VazI108CRrSKINQ=\ngithub.com/containers/storage v1.59.1 h1:11Zu68MXsEQGBBd+GadPrHPpWeqjKS8hJDGiAHgIqDs=\ngithub.com/containers/storage v1.59.1/go.mod h1:KoAYHnAjP3/cTsRS+mmWZGkufSY2GACiKQ4V3ZLQnR0=\ngithub.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=\ngithub.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=\ngithub.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=\ngithub.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=\ngithub.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=\ngithub.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE=\ngithub.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU=\ngithub.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=\ngithub.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=\ngithub.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=\ngithub.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=\ngithub.com/docker/cli v28.3.2+incompatible h1:mOt9fcLE7zaACbxW1GeS65RI67wIJrTnqS3hP2huFsY=\ngithub.com/docker/cli v28.3.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=\ngithub.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=\ngithub.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=\ngithub.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=\ngithub.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=\ngithub.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=\ngithub.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=\ngithub.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=\ngithub.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=\ngithub.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=\ngithub.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=\ngithub.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=\ngithub.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=\ngithub.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=\ngithub.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=\ngithub.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=\ngithub.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=\ngithub.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g=\ngithub.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/envoyproxy/protoc-gen-validate v1.3.0 h1:TvGH1wof4H33rezVKWSpqKz5NXWg5VPuZ0uONDT6eb4=\ngithub.com/envoyproxy/protoc-gen-validate v1.3.0/go.mod h1:HvYl7zwPa5mffgyeTUHA9zHIH36nmrm7oCbo4YKoSWA=\ngithub.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=\ngithub.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8=\ngithub.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=\ngithub.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=\ngithub.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=\ngithub.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=\ngithub.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=\ngithub.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=\ngithub.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=\ngithub.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=\ngithub.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=\ngithub.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=\ngithub.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=\ngithub.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY=\ngithub.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=\ngithub.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0 h1:skJKxRtNmevLqnayafdLe2AsenqRupVmzZSqrvb5caU=\ngithub.com/go-errors/errors v1.0.2-0.20180813162953-d98b870cc4e0/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=\ngithub.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=\ngithub.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=\ngithub.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=\ngithub.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=\ngithub.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=\ngithub.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=\ngithub.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=\ngithub.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=\ngithub.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=\ngithub.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=\ngithub.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=\ngithub.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=\ngithub.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=\ngithub.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=\ngithub.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=\ngithub.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=\ngithub.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M=\ngithub.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8=\ngithub.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=\ngithub.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=\ngithub.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=\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-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=\ngithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=\ngithub.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=\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.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.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=\ngithub.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=\ngithub.com/gonvenience/bunt v1.3.5 h1:wSQquifvwEWtzn27k1ngLfeLaStyt0k1b/K6TrlCNAs=\ngithub.com/gonvenience/bunt v1.3.5/go.mod h1:7ApqkVBEWvX04oJ28Q2WeI/BvJM6VtukaJAU/q/pTs8=\ngithub.com/gonvenience/neat v1.3.12 h1:xwIyRbJcG9LgcDYys+HHLH9DqqHeQsUpS5CfBUeskbs=\ngithub.com/gonvenience/neat v1.3.12/go.mod h1:8OljAIgPelN0uPPO94VBqxK+Kz98d6ZFwHDg5o/PfkE=\ngithub.com/gonvenience/term v1.0.2 h1:qKa2RydbWIrabGjR/fegJwpW5m+JvUwFL8mLhHzDXn0=\ngithub.com/gonvenience/term v1.0.2/go.mod h1:wThTR+3MzWtWn7XGVW6qQ65uaVf8GHED98KmwpuEQeo=\ngithub.com/gonvenience/text v1.0.7 h1:YmIqmgTwxnACYCG59DykgMbomwteYyNhAmEUEJtPl14=\ngithub.com/gonvenience/text v1.0.7/go.mod h1:OAjH+mohRszffLY6OjgQcUXiSkbrIavooFpfIt1ZwAs=\ngithub.com/gonvenience/wrap v1.1.2 h1:xPKxNwL1HCguwyM+HlP/1CIuc9LRd7k8RodLwe9YTZA=\ngithub.com/gonvenience/wrap v1.1.2/go.mod h1:GiryBSXoI3BAAhbWD1cZVj7RZmtiu0ERi/6R6eJfslI=\ngithub.com/gonvenience/ytbx v1.4.4 h1:jQopwyaLsVGuwdxSiN4WkXjsEaFNPJ3V4lUj7eyEpzo=\ngithub.com/gonvenience/ytbx v1.4.4/go.mod h1:w37+MKCPcCMY/jpPNmEklD4xKqrOAVBO6kIWW2+uI6M=\ngithub.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=\ngithub.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=\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.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=\ngithub.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=\ngithub.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=\ngithub.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI=\ngithub.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI=\ngithub.com/google/go-intervals v0.0.2 h1:FGrVEiUnTRKR8yE04qzXYaJMtnIYqobR5QbblK3ixcM=\ngithub.com/google/go-intervals v0.0.2/go.mod h1:MkaR3LNRfeKLPmqgJYs4E66z5InYjmCjbbr4TQlcT6Y=\ngithub.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=\ngithub.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=\ngithub.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=\ngithub.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=\ngithub.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=\ngithub.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=\ngithub.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=\ngithub.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=\ngithub.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=\ngithub.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=\ngithub.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=\ngithub.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=\ngithub.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=\ngithub.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=\ngithub.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=\ngithub.com/gruntwork-io/go-commons v0.8.0 h1:k/yypwrPqSeYHevLlEDmvmgQzcyTwrlZGRaxEM6G0ro=\ngithub.com/gruntwork-io/go-commons v0.8.0/go.mod h1:gtp0yTtIBExIZp7vyIV9I0XQkVwiQZze678hvDXof78=\ngithub.com/gruntwork-io/terratest v0.50.0 h1:AbBJ7IRCpLZ9H4HBrjeoWESITv8nLjN6/f1riMNcAsw=\ngithub.com/gruntwork-io/terratest v0.50.0/go.mod h1:see0lbKvAqz6rvzvN2wyfuFQQG4PWcAb2yHulF6B2q4=\ngithub.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=\ngithub.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=\ngithub.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=\ngithub.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=\ngithub.com/hashicorp/go-getter/v2 v2.2.3 h1:6CVzhT0KJQHqd9b0pK3xSP0CM/Cv+bVhk+jcaRJ2pGk=\ngithub.com/hashicorp/go-getter/v2 v2.2.3/go.mod h1:hp5Yy0GMQvwWVUmwLs3ygivz1JSLI323hdIE9J9m7TY=\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/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=\ngithub.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=\ngithub.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=\ngithub.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=\ngithub.com/hashicorp/hcl/v2 v2.22.0 h1:hkZ3nCtqeJsDhPRFz5EA9iwcG1hNWGePOTw6oyul12M=\ngithub.com/hashicorp/hcl/v2 v2.22.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=\ngithub.com/hashicorp/terraform-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2xoR+lppBkI=\ngithub.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c=\ngithub.com/homeport/dyff v1.6.0 h1:AN+ikld0Fy+qx34YE7655b/bpWuxS6cL9k852pE2GUc=\ngithub.com/homeport/dyff v1.6.0/go.mod h1:FlAOFYzeKvxmU5nTrnG+qrlJVWpsFew7pt8L99p5q8k=\ngithub.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=\ngithub.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=\ngithub.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=\ngithub.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=\ngithub.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=\ngithub.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=\ngithub.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=\ngithub.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=\ngithub.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=\ngithub.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=\ngithub.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o=\ngithub.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s=\ngithub.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=\ngithub.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=\ngithub.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=\ngithub.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=\ngithub.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=\ngithub.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=\ngithub.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=\ngithub.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=\ngithub.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=\ngithub.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=\ngithub.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=\ngithub.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=\ngithub.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=\ngithub.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=\ngithub.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=\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.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=\ngithub.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=\ngithub.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=\ngithub.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=\ngithub.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=\ngithub.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=\ngithub.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=\ngithub.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=\ngithub.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=\ngithub.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=\ngithub.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=\ngithub.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=\ngithub.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=\ngithub.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3 h1:BXxTozrOU8zgC5dkpn3J6NTRdoP+hjok/e+ACr4Hibk=\ngithub.com/mattn/go-ciede2000 v0.0.0-20170301095244-782e8c62fec3/go.mod h1:x1uk6vxTiVuNt6S5R2UYgdhpj3oKojXvOXauHZ7dEnI=\ngithub.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=\ngithub.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=\ngithub.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=\ngithub.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=\ngithub.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=\ngithub.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=\ngithub.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=\ngithub.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=\ngithub.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=\ngithub.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326 h1:ofNAzWCcyTALn2Zv40+8XitdzCgXY6e9qvXwN9W0YXg=\ngithub.com/mattn/go-zglob v0.0.2-0.20190814121620-e3c945676326/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=\ngithub.com/mistifyio/go-zfs/v3 v3.0.1 h1:YaoXgBePoMA12+S1u/ddkv+QqxcfiZK4prI6HPnkFiU=\ngithub.com/mistifyio/go-zfs/v3 v3.0.1/go.mod h1:CzVgeB0RvF2EGzQnytKVvVSDwmKJXxkOTUGbNrTja/k=\ngithub.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=\ngithub.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=\ngithub.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=\ngithub.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=\ngithub.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=\ngithub.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=\ngithub.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=\ngithub.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=\ngithub.com/mitchellh/hashstructure v1.1.0 h1:P6P1hdjqAAknpY/M1CGipelZgp+4y9ja9kmUZPXP+H0=\ngithub.com/mitchellh/hashstructure v1.1.0/go.mod h1:xUDAozZz0Wmdiufv0uyhnHkUTN6/6d8ulp4AwfLKrmA=\ngithub.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=\ngithub.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=\ngithub.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=\ngithub.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=\ngithub.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=\ngithub.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=\ngithub.com/moby/sys/capability v0.4.0 h1:4D4mI6KlNtWMCM1Z/K0i7RV1FkX+DBDHKVJpCndZoHk=\ngithub.com/moby/sys/capability v0.4.0/go.mod h1:4g9IK291rVkms3LKCDOoYlnV8xKwoDTpIrNEE35Wq0I=\ngithub.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=\ngithub.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=\ngithub.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=\ngithub.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=\ngithub.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=\ngithub.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=\ngithub.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=\ngithub.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=\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/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=\ngithub.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=\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/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=\ngithub.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=\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.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=\ngithub.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=\ngithub.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=\ngithub.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=\ngithub.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=\ngithub.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=\ngithub.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=\ngithub.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=\ngithub.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=\ngithub.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=\ngithub.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww=\ngithub.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=\ngithub.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8=\ngithub.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U=\ngithub.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=\ngithub.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=\ngithub.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=\ngithub.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=\ngithub.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=\ngithub.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=\ngithub.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=\ngithub.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=\ngithub.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=\ngithub.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=\ngithub.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=\ngithub.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=\ngithub.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=\ngithub.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=\ngithub.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=\ngithub.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=\ngithub.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=\ngithub.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=\ngithub.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=\ngithub.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=\ngithub.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=\ngithub.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=\ngithub.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=\ngithub.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=\ngithub.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=\ngithub.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=\ngithub.com/sebdah/goldie v1.0.0 h1:9GNhIat69MSlz/ndaBg48vl9dF5fI+NBB6kfOxgfkMc=\ngithub.com/sebdah/goldie/v2 v2.5.5 h1:rx1mwF95RxZ3/83sdS4Yp7t2C5TCokvWP4TBRbAyEWY=\ngithub.com/sebdah/goldie/v2 v2.5.5/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=\ngithub.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=\ngithub.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=\ngithub.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=\ngithub.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=\ngithub.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=\ngithub.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=\ngithub.com/slok/kubewebhook/v2 v2.5.0 h1:CwMxLbTEcha3+SxSXc4pc9iIbREdhgLurAs+/uRzxIw=\ngithub.com/slok/kubewebhook/v2 v2.5.0/go.mod h1:TcQS+Ae0TDiiwm9glxum6AFvtumR33qdAenUeiQ/TWs=\ngithub.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=\ngithub.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=\ngithub.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=\ngithub.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=\ngithub.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=\ngithub.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=\ngithub.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=\ngithub.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=\ngithub.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=\ngithub.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=\ngithub.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=\ngithub.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=\ngithub.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=\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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=\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/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=\ngithub.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=\ngithub.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=\ngithub.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=\ngithub.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=\ngithub.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=\ngithub.com/sylabs/sif/v2 v2.21.1 h1:GZ0b5//AFAqJEChd8wHV/uSKx/l1iuGYwjR8nx+4wPI=\ngithub.com/sylabs/sif/v2 v2.21.1/go.mod h1:YoqEGQnb5x/ItV653bawXHZJOXQaEWpGwHsSD3YePJI=\ngithub.com/tchap/go-patricia/v2 v2.3.3 h1:xfNEsODumaEcCcY3gI0hYPZ/PcpVv5ju6RMAhgwZDDc=\ngithub.com/tchap/go-patricia/v2 v2.3.3/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=\ngithub.com/texttheater/golang-levenshtein v1.0.1 h1:+cRNoVrfiwufQPhoMzB6N0Yf/Mqajr6t1lOv8GyGE2U=\ngithub.com/texttheater/golang-levenshtein v1.0.1/go.mod h1:PYAKrbF5sAiq9wd+H82hs7gNaen0CplQ9uvm6+enD/8=\ngithub.com/tmccombs/hcl2json v0.6.4 h1:/FWnzS9JCuyZ4MNwrG4vMrFrzRgsWEOVi+1AyYUVLGw=\ngithub.com/tmccombs/hcl2json v0.6.4/go.mod h1:+ppKlIW3H5nsAsZddXPy2iMyvld3SHxyjswOZhavRDk=\ngithub.com/ulikunitz/xz v0.5.14 h1:uv/0Bq533iFdnMHZdRBTOlaNMdb1+ZxXIlHDZHIHcvg=\ngithub.com/ulikunitz/xz v0.5.14/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=\ngithub.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=\ngithub.com/urfave/cli v1.22.16 h1:MH0k6uJxdwdeWQTwhSO42Pwr4YLrNLwBtg1MRgTqPdQ=\ngithub.com/urfave/cli v1.22.16/go.mod h1:EeJR6BKodywf4zciqrdw6hpCPk68JO9z5LazXZMn5Po=\ngithub.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo=\ngithub.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA=\ngithub.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74 h1:JwtAtbp7r/7QSyGz8mKUbYJBg2+6Cd7OjM8o/GNOcVo=\ngithub.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:RmMWU37GKR2s6pgrIEB4ixgpVCt/cf7dnJv3fuH1J1c=\ngithub.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=\ngithub.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=\ngithub.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=\ngithub.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ=\ngithub.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=\ngithub.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=\ngithub.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=\ngo.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=\ngo.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=\ngo.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=\ngo.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=\ngo.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=\ngo.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=\ngo.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=\ngo.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg=\ngo.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk=\ngo.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=\ngo.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=\ngo.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=\ngo.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=\ngo.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=\ngo.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=\ngo.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=\ngo.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=\ngo.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os=\ngo.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo=\ngo.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=\ngo.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=\ngo.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=\ngo.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=\ngo.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=\ngo.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=\ngo.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=\ngo.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=\ngolang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=\ngolang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=\ngolang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=\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-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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=\ngolang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=\ngolang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=\ngolang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=\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-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.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=\ngolang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/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-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20210616094352-59db8d763f22/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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=\ngolang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=\ngolang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=\ngolang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=\ngolang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=\ngolang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=\ngolang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=\ngolang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=\ngolang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=\ngolang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=\ngolang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=\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/v3 v3.0.1 h1:Te7hKxV52TKCbNYq3t84tzKav3xhThdvSsSp/W89IyI=\ngomodules.xyz/jsonpatch/v3 v3.0.1/go.mod h1:CBhndykehEwTOlEfnsfJwvkFQbSN8YZFr9M+cIHAJto=\ngomodules.xyz/orderedmap v0.1.0 h1:fM/+TGh/O1KkqGR5xjTKg6bU8OKBkg7p0Y+x/J9m8Os=\ngomodules.xyz/orderedmap v0.1.0/go.mod h1:g9/TPUCm1t2gwD3j3zfV8uylyYhVdCNSi+xCEIu7yTU=\ngonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=\ngonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=\ngoogle.golang.org/api v0.250.0 h1:qvkwrf/raASj82UegU2RSDGWi/89WkLckn4LuO4lVXM=\ngoogle.golang.org/api v0.250.0/go.mod h1:Y9Uup8bDLJJtMzJyQnu+rLRJLA0wn+wTtc6vTlOvfXo=\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/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/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4=\ngoogle.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=\ngoogle.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=\ngoogle.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=\ngoogle.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=\ngoogle.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=\ngoogle.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=\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.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=\ngoogle.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=\ngopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=\ngopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=\ngopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=\ngopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=\ngopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=\ngopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=\ngopkg.in/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.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=\ngopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=\ngopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=\ngotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=\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.33.4 h1:oTzrFVNPXBjMu0IlpA2eDDIU49jsuEorGHB4cvKupkk=\nk8s.io/api v0.33.4/go.mod h1:VHQZ4cuxQ9sCUMESJV5+Fe8bGnqAARZ08tSTdHWfeAc=\nk8s.io/apimachinery v0.33.4 h1:SOf/JW33TP0eppJMkIgQ+L6atlDiP/090oaX0y9pd9s=\nk8s.io/apimachinery v0.33.4/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM=\nk8s.io/client-go v0.33.4 h1:TNH+CSu8EmXfitntjUPwaKVPN0AYMbc9F1bBS8/ABpw=\nk8s.io/client-go v0.33.4/go.mod h1:LsA0+hBG2DPwovjd931L/AoaezMPX9CmBgyVyBZmbCY=\nk8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=\nk8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=\nk8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=\nk8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=\nk8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=\nk8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=\nsigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=\nsigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=\nsigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=\nsigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=\nsigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=\nsigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=\nsigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=\nsigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=\nsigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=\n"
  },
  {
    "path": "main.go",
    "content": "/*\nCopyright © 2020 Enrico Stahn <enrico.stahn@gmail.com>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n*/\npackage main\n\nimport \"github.com/estahn/k8s-image-swapper/cmd\"\n\nfunc main() {\n\tcmd.Execute()\n}\n"
  },
  {
    "path": "mkdocs.yml",
    "content": "# Project information\nsite_name: k8s-image-swapper\nsite_url: https://estahn.github.io/k8s-image-swapper/\nsite_author: Enrico Stahn\nsite_description: >-\n  Mirror images into your own registry and swap image references automatically.\n\n# Repository\nrepo_name: estahn/k8s-image-swapper\nrepo_url: https://github.com/estahn/k8s-image-swapper\nedit_uri: \"blob/main/docs/\"\n\n# Copyright\ncopyright: Copyright &copy; 2020 Enrico Stahn\n\nuse_directory_urls: false\n\ntheme:\n  name: material\n  custom_dir: docs/overrides\n\n  palette:\n\n    # Palette toggle for automatic mode\n    - media: \"(prefers-color-scheme)\"\n      toggle:\n        icon: material/brightness-auto\n        name: Switch to light mode\n\n    # Palette toggle for light mode\n    - media: \"(prefers-color-scheme: light)\"\n      scheme: default\n      toggle:\n        icon: material/brightness-7\n        name: Switch to dark mode\n\n    # Palette toggle for dark mode\n    - media: \"(prefers-color-scheme: dark)\"\n      scheme: slate\n      toggle:\n        icon: material/brightness-4\n        name: Switch to system preference\n\n\n\n  # Don't include MkDocs' JavaScript\n  include_search_page: false\n  search_index_only: true\n\n  # Default values, taken from mkdocs_theme.yml\n  language: en\n\n  features:\n    - tabs\n    - content.action.edit\n    - content.code.copy\n    - navigation.footer\n\n# Plugins\nplugins:\n  - search\n  - minify:\n      minify_html: true\n  - markdownextradata: {}\n  - social\n\n# Extensions\nmarkdown_extensions:\n  - admonition\n  - attr_list\n  - md_in_html\n  - codehilite:\n      guess_lang: false\n  - def_list\n  - footnotes\n  - meta\n  - toc:\n      permalink: true\n  - pymdownx.arithmatex\n  - pymdownx.betterem:\n      smart_enable: all\n  - pymdownx.caret\n  - pymdownx.critic\n  - pymdownx.details\n  - pymdownx.emoji\n  - pymdownx.highlight:\n      use_pygments: true\n      linenums_style: pymdownx-inline\n      anchor_linenums: true\n  - pymdownx.inlinehilite\n  - pymdownx.keys\n  - pymdownx.magiclink:\n      repo_url_shorthand: true\n      user: squidfunk\n      repo: mkdocs-material\n  - pymdownx.mark\n  - pymdownx.smartsymbols\n  - pymdownx.snippets:\n      check_paths: true\n  - pymdownx.superfences\n  - pymdownx.tabbed:\n      alternate_style: true\n  - pymdownx.tasklist:\n      custom_checkbox: true\n  - pymdownx.tilde\n\nnav:\n  - Home: index.md\n  - Getting started: getting-started.md\n  - Configuration: configuration.md\n  - FAQ: faq.md\n#  - Releases:\n#      - 1.3.0: releases/1.3.0-NOTES.md\n#  - Operations:\n#    - Production considerations: foo\n#  - Contributing:\n#    - Testing: testing.md\n#    - Contributors: constributors.md\n\nextra:\n  version:\n    provider: mike\n    default: latest\n  social:\n    - icon: fontawesome/brands/github\n      link: https://github.com/estahn/k8s-image-swapper\n    - icon: fontawesome/brands/docker\n      link: https://github.com/estahn/k8s-image-swapper/pkgs/container/k8s-image-swapper\n    - icon: fontawesome/brands/slack\n      link: https://kubernetes.slack.com/archives/C04LETF7KEC\n    - icon: fontawesome/brands/twitter\n      link: https://twitter.com/estahn\n    - icon: fontawesome/brands/linkedin\n      link: https://www.linkedin.com/in/enricostahn\n  analytics:\n    provider: google\n    property: G-BK225DNZVM\n    feedback:\n      title: Was this page helpful?\n      ratings:\n        - icon: material/emoticon-happy-outline\n          name: This page was helpful\n          data: 1\n          note: >-\n            Thanks for your feedback!\n        - icon: material/emoticon-sad-outline\n          name: This page could be improved\n          data: 0\n          note: >-\n            Thanks for your feedback! Help us improve this page by\n            using our <a href=\"...\" target=\"_blank\" rel=\"noopener\">feedback form</a>.\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"devDependencies\": {\n    \"@semantic-release/changelog\": \"^6.0.3\",\n    \"@semantic-release/exec\": \"^7.1.0\",\n    \"@semantic-release/git\": \"^10.0.1\",\n    \"conventional-changelog-conventionalcommits\": \"^9.3.1\",\n    \"semantic-release\": \"^25.0.3\"\n  }\n}\n"
  },
  {
    "path": "pkg/config/config.go",
    "content": "/*\nCopyright © 2020 Enrico Stahn <enrico.stahn@gmail.com>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n*/\npackage config\n\nimport (\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/spf13/viper\"\n\n\t\"github.com/estahn/k8s-image-swapper/pkg/types\"\n)\n\nconst DefaultImageCopyDeadline = 8 * time.Second\n\ntype Config struct {\n\tLogLevel  string `yaml:\"logLevel\" validate:\"oneof=trace debug info warn error fatal\"`\n\tLogFormat string `yaml:\"logFormat\" validate:\"oneof=json console\"`\n\n\tListenAddress string\n\n\tDryRun            bool          `yaml:\"dryRun\"`\n\tImageSwapPolicy   string        `yaml:\"imageSwapPolicy\" validate:\"oneof=always exists\"`\n\tImageCopyPolicy   string        `yaml:\"imageCopyPolicy\" validate:\"oneof=delayed immediate force none\"`\n\tImageCopyDeadline time.Duration `yaml:\"imageCopyDeadline\"`\n\n\tSource Source   `yaml:\"source\"`\n\tTarget Registry `yaml:\"target\"`\n\n\tTLSCertFile string\n\tTLSKeyFile  string\n}\n\ntype JMESPathFilter struct {\n\tJMESPath string `yaml:\"jmespath\"`\n}\n\ntype Source struct {\n\tRegistries []Registry       `yaml:\"registries\"`\n\tFilters    []JMESPathFilter `yaml:\"filters\"`\n}\n\ntype Registry struct {\n\tType string `yaml:\"type\"`\n\tAWS  AWS    `yaml:\"aws\"`\n\tGCP  GCP    `yaml:\"gcp\"`\n}\n\ntype AWS struct {\n\tAccountID  string     `yaml:\"accountId\"`\n\tRegion     string     `yaml:\"region\"`\n\tRole       string     `yaml:\"role\"`\n\tPrefix     string     `yaml:\"prefix\"`\n\tECROptions ECROptions `yaml:\"ecrOptions\"`\n}\n\ntype GCP struct {\n\tLocation     string `yaml:\"location\"`\n\tProjectID    string `yaml:\"projectId\"`\n\tRepositoryID string `yaml:\"repositoryId\"`\n}\n\ntype ECROptions struct {\n\tAccessPolicy               string                     `yaml:\"accessPolicy\"`\n\tLifecyclePolicy            string                     `yaml:\"lifecyclePolicy\"`\n\tTags                       []Tag                      `yaml:\"tags\"`\n\tImageTagMutability         string                     `yaml:\"imageTagMutability\"  validate:\"oneof=MUTABLE IMMUTABLE\"`\n\tImageScanningConfiguration ImageScanningConfiguration `yaml:\"imageScanningConfiguration\"`\n\tEncryptionConfiguration    EncryptionConfiguration    `yaml:\"encryptionConfiguration\"`\n}\n\ntype Tag struct {\n\tKey   string `yaml:\"key\"`\n\tValue string `yaml:\"value\"`\n}\n\ntype ImageScanningConfiguration struct {\n\tImageScanOnPush bool `yaml:\"imageScanOnPush\"`\n}\n\ntype EncryptionConfiguration struct {\n\tEncryptionType string `yaml:\"encryptionType\" validate:\"oneof=KMS AES256\"`\n\tKmsKey         string `yaml:\"kmsKey\"`\n}\n\nfunc (a *AWS) EcrDomain() string {\n\tdomain := \"amazonaws.com\"\n\tif strings.HasPrefix(a.Region, \"cn-\") {\n\t\tdomain = \"amazonaws.com.cn\"\n\t}\n\treturn fmt.Sprintf(\"%s.dkr.ecr.%s.%s%s\", a.AccountID, a.Region, domain, a.Prefix)\n}\n\nfunc (g *GCP) GarDomain() string {\n\treturn fmt.Sprintf(\"%s-docker.pkg.dev/%s/%s\", g.Location, g.ProjectID, g.RepositoryID)\n}\n\nfunc (r Registry) Domain() string {\n\tregistry, _ := types.ParseRegistry(r.Type)\n\tswitch registry {\n\tcase types.RegistryAWS:\n\t\treturn r.AWS.EcrDomain()\n\tcase types.RegistryGCP:\n\t\treturn r.GCP.GarDomain()\n\tdefault:\n\t\treturn \"\"\n\t}\n}\n\n// provides detailed information about wrongly provided configuration\nfunc CheckRegistryConfiguration(r Registry) error {\n\tif r.Type == \"\" {\n\t\treturn fmt.Errorf(\"a registry requires a type\")\n\t}\n\n\terrorWithType := func(info string) error {\n\t\treturn fmt.Errorf(`registry of type \"%s\" %s`, r.Type, info)\n\t}\n\n\tregistry, _ := types.ParseRegistry(r.Type)\n\tswitch registry {\n\tcase types.RegistryAWS:\n\t\tif r.AWS.Region == \"\" {\n\t\t\treturn errorWithType(`requires a field \"region\"`)\n\t\t}\n\t\tif r.AWS.AccountID == \"\" {\n\t\t\treturn errorWithType(`requires a field \"accountdId\"`)\n\t\t}\n\t\tif r.AWS.ECROptions.EncryptionConfiguration.EncryptionType == \"KMS\" && r.AWS.ECROptions.EncryptionConfiguration.KmsKey == \"\" {\n\t\t\treturn errorWithType(`requires a field \"kmsKey\" if encryptionType is set to \"KMS\"`)\n\t\t}\n\tcase types.RegistryGCP:\n\t\tif r.GCP.Location == \"\" {\n\t\t\treturn errorWithType(`requires a field \"location\"`)\n\t\t}\n\t\tif r.GCP.ProjectID == \"\" {\n\t\t\treturn errorWithType(`requires a field \"projectId\"`)\n\t\t}\n\t\tif r.GCP.RepositoryID == \"\" {\n\t\t\treturn errorWithType(`requires a field \"repositoryId\"`)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// SetViperDefaults configures default values for config items that are not set.\nfunc SetViperDefaults(v *viper.Viper) {\n\tv.SetDefault(\"Target.Type\", \"aws\")\n\tv.SetDefault(\"Target.AWS.ECROptions.ImageScanningConfiguration.ImageScanOnPush\", true)\n\tv.SetDefault(\"Target.AWS.ECROptions.ImageTagMutability\", \"MUTABLE\")\n\tv.SetDefault(\"Target.AWS.ECROptions.EncryptionConfiguration.EncryptionType\", \"AES256\")\n}\n"
  },
  {
    "path": "pkg/config/config_test.go",
    "content": "package config\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/spf13/viper\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\n// TestConfigParses validates if yaml annotation do not overlap\nfunc TestConfigParses(t *testing.T) {\n\ttests := []struct {\n\t\tname   string\n\t\tcfg    string\n\t\texpCfg Config\n\t\texpErr bool\n\t}{\n\t\t{\n\t\t\tname: \"should render empty config with defaults\",\n\t\t\tcfg:  \"\",\n\t\t\texpCfg: Config{\n\t\t\t\tTarget: Registry{\n\t\t\t\t\tType: \"aws\",\n\t\t\t\t\tAWS: AWS{\n\t\t\t\t\t\tECROptions: ECROptions{\n\t\t\t\t\t\t\tImageTagMutability: \"MUTABLE\",\n\t\t\t\t\t\t\tImageScanningConfiguration: ImageScanningConfiguration{\n\t\t\t\t\t\t\t\tImageScanOnPush: true,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tEncryptionConfiguration: EncryptionConfiguration{\n\t\t\t\t\t\t\t\tEncryptionType: \"AES256\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"should render multiple filters\",\n\t\t\tcfg: `\nsource:\n  filters:\n    - jmespath: \"obj.metadata.namespace == 'kube-system'\"\n    - jmespath: \"obj.metadata.namespace != 'playground'\"\n`,\n\t\t\texpCfg: Config{\n\t\t\t\tTarget: Registry{\n\t\t\t\t\tType: \"aws\",\n\t\t\t\t\tAWS: AWS{\n\t\t\t\t\t\tECROptions: ECROptions{\n\t\t\t\t\t\t\tImageTagMutability: \"MUTABLE\",\n\t\t\t\t\t\t\tImageScanningConfiguration: ImageScanningConfiguration{\n\t\t\t\t\t\t\t\tImageScanOnPush: true,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tEncryptionConfiguration: EncryptionConfiguration{\n\t\t\t\t\t\t\t\tEncryptionType: \"AES256\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSource: Source{\n\t\t\t\t\tFilters: []JMESPathFilter{\n\t\t\t\t\t\t{JMESPath: \"obj.metadata.namespace == 'kube-system'\"},\n\t\t\t\t\t\t{JMESPath: \"obj.metadata.namespace != 'playground'\"},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"should render tags config\",\n\t\t\tcfg: `\ntarget:\n  type: aws\n  aws:\n    accountId: 123456789\n    region: ap-southeast-2\n    role: arn:aws:iam::123456789012:role/roleName\n    ecrOptions:\n      tags:\n        - key: CreatedBy\n          value: k8s-image-swapper\n        - key: A\n          value: B\n`,\n\t\t\texpCfg: Config{\n\t\t\t\tTarget: Registry{\n\t\t\t\t\tType: \"aws\",\n\t\t\t\t\tAWS: AWS{\n\t\t\t\t\t\tAccountID: \"123456789\",\n\t\t\t\t\t\tRegion:    \"ap-southeast-2\",\n\t\t\t\t\t\tRole:      \"arn:aws:iam::123456789012:role/roleName\",\n\t\t\t\t\t\tECROptions: ECROptions{\n\t\t\t\t\t\t\tImageTagMutability: \"MUTABLE\",\n\t\t\t\t\t\t\tImageScanningConfiguration: ImageScanningConfiguration{\n\t\t\t\t\t\t\t\tImageScanOnPush: true,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tEncryptionConfiguration: EncryptionConfiguration{\n\t\t\t\t\t\t\t\tEncryptionType: \"AES256\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tTags: []Tag{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tKey:   \"CreatedBy\",\n\t\t\t\t\t\t\t\t\tValue: \"k8s-image-swapper\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tKey:   \"A\",\n\t\t\t\t\t\t\t\t\tValue: \"B\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"should render multiple source registries\",\n\t\t\tcfg: `\nsource:\n  registries:\n    - type: \"aws\"\n      aws:\n        accountId: \"12345678912\"\n        region: \"us-west-1\"\n    - type: \"aws\"\n      aws:\n        accountId: \"12345678912\"\n        region: \"us-east-1\"\n`,\n\t\t\texpCfg: Config{\n\t\t\t\tTarget: Registry{\n\t\t\t\t\tType: \"aws\",\n\t\t\t\t\tAWS: AWS{\n\t\t\t\t\t\tECROptions: ECROptions{\n\t\t\t\t\t\t\tImageTagMutability: \"MUTABLE\",\n\t\t\t\t\t\t\tImageScanningConfiguration: ImageScanningConfiguration{\n\t\t\t\t\t\t\t\tImageScanOnPush: true,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tEncryptionConfiguration: EncryptionConfiguration{\n\t\t\t\t\t\t\t\tEncryptionType: \"AES256\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tSource: Source{\n\t\t\t\t\tRegistries: []Registry{\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tType: \"aws\",\n\t\t\t\t\t\t\tAWS: AWS{\n\t\t\t\t\t\t\t\tAccountID: \"12345678912\",\n\t\t\t\t\t\t\t\tRegion:    \"us-west-1\",\n\t\t\t\t\t\t\t}},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tType: \"aws\",\n\t\t\t\t\t\t\tAWS: AWS{\n\t\t\t\t\t\t\t\tAccountID: \"12345678912\",\n\t\t\t\t\t\t\t\tRegion:    \"us-east-1\",\n\t\t\t\t\t\t\t}},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tname: \"should use previous defaults\",\n\t\t\tcfg: `\ntarget:\n  type: aws\n  aws:\n    accountId: 123456789\n    region: ap-southeast-2\n    role: arn:aws:iam::123456789012:role/roleName\n    ecrOptions:\n      tags:\n        - key: CreatedBy\n          value: k8s-image-swapper\n        - key: A\n          value: B\n`,\n\t\t\texpCfg: Config{\n\t\t\t\tTarget: Registry{\n\t\t\t\t\tType: \"aws\",\n\t\t\t\t\tAWS: AWS{\n\t\t\t\t\t\tAccountID: \"123456789\",\n\t\t\t\t\t\tRegion:    \"ap-southeast-2\",\n\t\t\t\t\t\tRole:      \"arn:aws:iam::123456789012:role/roleName\",\n\t\t\t\t\t\tECROptions: ECROptions{\n\t\t\t\t\t\t\tImageScanningConfiguration: ImageScanningConfiguration{\n\t\t\t\t\t\t\t\tImageScanOnPush: true,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tEncryptionConfiguration: EncryptionConfiguration{\n\t\t\t\t\t\t\t\tEncryptionType: \"AES256\",\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tImageTagMutability: \"MUTABLE\",\n\t\t\t\t\t\t\tTags: []Tag{\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tKey:   \"CreatedBy\",\n\t\t\t\t\t\t\t\t\tValue: \"k8s-image-swapper\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tKey:   \"A\",\n\t\t\t\t\t\t\t\t\tValue: \"B\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t}\n\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tassert := assert.New(t)\n\n\t\t\tv := viper.New()\n\t\t\tv.SetConfigType(\"yaml\")\n\t\t\tSetViperDefaults(v)\n\n\t\t\treadConfigError := v.ReadConfig(strings.NewReader(test.cfg))\n\t\t\tassert.NoError(readConfigError)\n\n\t\t\tgotCfg := Config{}\n\t\t\terr := v.Unmarshal(&gotCfg)\n\n\t\t\tif test.expErr {\n\t\t\t\tassert.Error(err)\n\t\t\t} else if assert.NoError(err) {\n\t\t\t\tassert.Equal(test.expCfg, gotCfg)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestEcrDomain(t *testing.T) {\n\ttests := []struct {\n\t\tname   string\n\t\tcfg    Config\n\t\tdomain string\n\t}{\n\t\t{\n\t\t\tname: \"commercial aws\",\n\t\t\tcfg: Config{\n\t\t\t\tTarget: Registry{\n\t\t\t\t\tType: \"aws\",\n\t\t\t\t\tAWS: AWS{\n\t\t\t\t\t\tAccountID: \"123456789\",\n\t\t\t\t\t\tRegion:    \"ap-southeast-2\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tdomain: \"123456789.dkr.ecr.ap-southeast-2.amazonaws.com\",\n\t\t},\n\t\t{\n\t\t\tname: \"aws in china\",\n\t\t\tcfg: Config{\n\t\t\t\tTarget: Registry{\n\t\t\t\t\tType: \"aws\",\n\t\t\t\t\tAWS: AWS{\n\t\t\t\t\t\tAccountID: \"123456789\",\n\t\t\t\t\t\tRegion:    \"cn-north-1\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tdomain: \"123456789.dkr.ecr.cn-north-1.amazonaws.com.cn\",\n\t\t},\n\t\t{\n\t\t\tname: \"aws with prefix\",\n\t\t\tcfg: Config{\n\t\t\t\tTarget: Registry{\n\t\t\t\t\tType: \"aws\",\n\t\t\t\t\tAWS: AWS{\n\t\t\t\t\t\tAccountID: \"123456789\",\n\t\t\t\t\t\tRegion:    \"ap-southeast-2\",\n\t\t\t\t\t\tPrefix:    \"/prefix\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tdomain: \"123456789.dkr.ecr.ap-southeast-2.amazonaws.com/prefix\",\n\t\t},\n\t}\n\tfor _, test := range tests {\n\t\tt.Run(test.name, func(t *testing.T) {\n\t\t\tassert := assert.New(t)\n\t\t\tassert.Equal(test.cfg.Target.AWS.EcrDomain(), test.domain)\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/registry/client.go",
    "content": "package registry\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\n\t\"github.com/estahn/k8s-image-swapper/pkg/config\"\n\t\"github.com/estahn/k8s-image-swapper/pkg/types\"\n\n\tctypes \"github.com/containers/image/v5/types\"\n)\n\n// Client provides methods required to be implemented by the various target registry clients, e.g. ECR, Docker, Quay.\ntype Client interface {\n\tCreateRepository(ctx context.Context, name string) error\n\tRepositoryExists() bool\n\tCopyImage(ctx context.Context, src ctypes.ImageReference, srcCreds string, dest ctypes.ImageReference, destCreds string) error\n\tPullImage() error\n\tPutImage() error\n\tImageExists(ctx context.Context, ref ctypes.ImageReference) bool\n\n\t// Endpoint returns the domain of the registry\n\tEndpoint() string\n\tCredentials() string\n\n\t// IsOrigin returns true if the imageRef originates from this registry\n\tIsOrigin(imageRef ctypes.ImageReference) bool\n}\n\ntype DockerConfig struct {\n\tAuthConfigs map[string]AuthConfig `json:\"auths\"`\n}\n\ntype AuthConfig struct {\n\tAuth string `json:\"auth,omitempty\"`\n}\n\n// NewClient returns a registry client ready for use without the need to specify an implementation\nfunc NewClient(r config.Registry) (Client, error) {\n\tif err := config.CheckRegistryConfiguration(r); err != nil {\n\t\treturn nil, err\n\t}\n\n\tregistry, err := types.ParseRegistry(r.Type)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tswitch registry {\n\tcase types.RegistryAWS:\n\t\treturn NewECRClient(r.AWS)\n\tcase types.RegistryGCP:\n\t\treturn NewGARClient(r.GCP)\n\tdefault:\n\t\treturn nil, fmt.Errorf(`registry of type \"%s\" is not supported`, r.Type)\n\t}\n}\n\nfunc GenerateDockerConfig(c Client) ([]byte, error) {\n\tdockerConfig := DockerConfig{\n\t\tAuthConfigs: map[string]AuthConfig{\n\t\t\tc.Endpoint(): {\n\t\t\t\tAuth: base64.StdEncoding.EncodeToString([]byte(c.Credentials())),\n\t\t\t},\n\t\t},\n\t}\n\n\tdockerConfigJson, err := json.Marshal(dockerConfig)\n\tif err != nil {\n\t\treturn []byte{}, err\n\t}\n\n\treturn dockerConfigJson, nil\n}\n"
  },
  {
    "path": "pkg/registry/ecr.go",
    "content": "package registry\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"net/http\"\n\t\"os/exec\"\n\t\"time\"\n\n\t\"github.com/containers/image/v5/docker/reference\"\n\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/aws/awserr\"\n\t\"github.com/aws/aws-sdk-go/aws/credentials/stscreds\"\n\t\"github.com/aws/aws-sdk-go/aws/session\"\n\t\"github.com/aws/aws-sdk-go/service/ecr\"\n\t\"github.com/aws/aws-sdk-go/service/ecr/ecriface\"\n\tctypes \"github.com/containers/image/v5/types\"\n\t\"github.com/dgraph-io/ristretto\"\n\t\"github.com/estahn/k8s-image-swapper/pkg/config\"\n\t\"github.com/go-co-op/gocron\"\n\t\"github.com/rs/zerolog/log\"\n)\n\ntype ECRClient struct {\n\tclient        ecriface.ECRAPI\n\tecrDomain     string\n\tauthToken     []byte\n\tcache         *ristretto.Cache\n\tscheduler     *gocron.Scheduler\n\ttargetAccount string\n\toptions       config.ECROptions\n}\n\nfunc NewECRClient(clientConfig config.AWS) (*ECRClient, error) {\n\tecrDomain := clientConfig.EcrDomain()\n\n\tvar sess *session.Session\n\tvar cfg *aws.Config\n\tif clientConfig.Role != \"\" {\n\t\tlog.Info().Str(\"assumedRole\", clientConfig.Role).Msg(\"assuming specified role\")\n\t\tstsSession, _ := session.NewSession(cfg)\n\t\tcreds := stscreds.NewCredentials(stsSession, clientConfig.Role)\n\t\tcfg = aws.NewConfig().\n\t\t\tWithRegion(clientConfig.Region).\n\t\t\tWithCredentialsChainVerboseErrors(true).\n\t\t\tWithHTTPClient(&http.Client{\n\t\t\t\tTimeout: 3 * time.Second,\n\t\t\t}).\n\t\t\tWithCredentials(creds)\n\t} else {\n\t\tcfg = aws.NewConfig().\n\t\t\tWithRegion(clientConfig.Region).\n\t\t\tWithCredentialsChainVerboseErrors(true).\n\t\t\tWithHTTPClient(&http.Client{\n\t\t\t\tTimeout: 3 * time.Second,\n\t\t\t})\n\t}\n\n\tsess = session.Must(session.NewSessionWithOptions(session.Options{\n\t\tSharedConfigState: session.SharedConfigEnable,\n\t\tConfig:            *cfg,\n\t}))\n\tecrClient := ecr.New(sess, cfg)\n\n\tcache, err := ristretto.NewCache(&ristretto.Config{\n\t\tNumCounters: 1e7,     // number of keys to track frequency of (10M).\n\t\tMaxCost:     1 << 30, // maximum cost of cache (1GB).\n\t\tBufferItems: 64,      // number of keys per Get buffer.\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tscheduler := gocron.NewScheduler(time.UTC)\n\tscheduler.StartAsync()\n\n\tclient := &ECRClient{\n\t\tclient:        ecrClient,\n\t\tecrDomain:     ecrDomain,\n\t\tcache:         cache,\n\t\tscheduler:     scheduler,\n\t\ttargetAccount: clientConfig.AccountID,\n\t\toptions:       clientConfig.ECROptions,\n\t}\n\n\tif err := client.scheduleTokenRenewal(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn client, nil\n}\n\nfunc (e *ECRClient) Credentials() string {\n\treturn string(e.authToken)\n}\n\nfunc (e *ECRClient) CreateRepository(ctx context.Context, name string) error {\n\tif _, found := e.cache.Get(name); found {\n\t\treturn nil\n\t}\n\n\tlog.Ctx(ctx).Debug().Str(\"repository\", name).Msg(\"create repository\")\n\n\tencryptionConfiguration := &ecr.EncryptionConfiguration{\n\t\tEncryptionType: aws.String(e.options.EncryptionConfiguration.EncryptionType),\n\t}\n\n\tif e.options.EncryptionConfiguration.EncryptionType == \"KMS\" {\n\t\tencryptionConfiguration.KmsKey = aws.String(e.options.EncryptionConfiguration.KmsKey)\n\t}\n\n\t_, err := e.client.CreateRepositoryWithContext(ctx, &ecr.CreateRepositoryInput{\n\t\tRepositoryName:          aws.String(name),\n\t\tEncryptionConfiguration: encryptionConfiguration,\n\t\tImageScanningConfiguration: &ecr.ImageScanningConfiguration{\n\t\t\tScanOnPush: aws.Bool(e.options.ImageScanningConfiguration.ImageScanOnPush),\n\t\t},\n\t\tImageTagMutability: aws.String(e.options.ImageTagMutability),\n\t\tRegistryId:         &e.targetAccount,\n\t\tTags:               e.buildEcrTags(),\n\t})\n\n\tif err != nil {\n\t\tif aerr, ok := err.(awserr.Error); ok {\n\t\t\tswitch aerr.Code() {\n\t\t\tcase ecr.ErrCodeRepositoryAlreadyExistsException:\n\t\t\t\t// We ignore this case as it is valid.\n\t\t\tdefault:\n\t\t\t\treturn err\n\t\t\t}\n\t\t} else {\n\t\t\t// Print the error, cast err to awserr.Error to get the Code and\n\t\t\t// Message from an error.\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif len(e.options.AccessPolicy) > 0 {\n\t\tlog.Ctx(ctx).Debug().Str(\"repo\", name).Str(\"accessPolicy\", e.options.AccessPolicy).Msg(\"setting access policy on repo\")\n\t\t_, err := e.client.SetRepositoryPolicyWithContext(ctx, &ecr.SetRepositoryPolicyInput{\n\t\t\tPolicyText:     &e.options.AccessPolicy,\n\t\t\tRegistryId:     &e.targetAccount,\n\t\t\tRepositoryName: aws.String(name),\n\t\t})\n\n\t\tif err != nil {\n\t\t\tlog.Err(err).Msg(err.Error())\n\t\t\treturn err\n\t\t}\n\t}\n\n\tif len(e.options.LifecyclePolicy) > 0 {\n\t\tlog.Ctx(ctx).Debug().Str(\"repo\", name).Str(\"lifecyclePolicy\", e.options.LifecyclePolicy).Msg(\"setting lifecycle policy on repo\")\n\t\t_, err := e.client.PutLifecyclePolicyWithContext(ctx, &ecr.PutLifecyclePolicyInput{\n\t\t\tLifecyclePolicyText: &e.options.LifecyclePolicy,\n\t\t\tRegistryId:          &e.targetAccount,\n\t\t\tRepositoryName:      aws.String(name),\n\t\t})\n\n\t\tif err != nil {\n\t\t\tlog.Err(err).Msg(err.Error())\n\t\t\treturn err\n\t\t}\n\t}\n\n\te.cache.SetWithTTL(name, \"\", 1, time.Duration(24*time.Hour))\n\n\treturn nil\n}\n\nfunc (e *ECRClient) buildEcrTags() []*ecr.Tag {\n\tecrTags := []*ecr.Tag{}\n\n\tfor _, t := range e.options.Tags {\n\t\ttag := ecr.Tag{Key: aws.String(t.Key), Value: aws.String(t.Value)}\n\t\tecrTags = append(ecrTags, &tag)\n\t}\n\n\treturn ecrTags\n}\n\nfunc (e *ECRClient) RepositoryExists() bool {\n\tpanic(\"implement me\")\n}\n\nfunc (e *ECRClient) CopyImage(ctx context.Context, srcRef ctypes.ImageReference, srcCreds string, destRef ctypes.ImageReference, destCreds string) error {\n\tsrc := srcRef.DockerReference().String()\n\tdest := destRef.DockerReference().String()\n\tapp := \"skopeo\"\n\targs := []string{\n\t\t\"--override-os\", \"linux\",\n\t\t\"copy\",\n\t\t\"--multi-arch\", \"all\",\n\t\t\"--retry-times\", \"3\",\n\t\t\"docker://\" + src,\n\t\t\"docker://\" + dest,\n\t}\n\n\tif len(srcCreds) > 0 {\n\t\targs = append(args, \"--src-authfile\", srcCreds)\n\t} else {\n\t\targs = append(args, \"--src-no-creds\")\n\t}\n\n\tif len(destCreds) > 0 {\n\t\targs = append(args, \"--dest-creds\", destCreds)\n\t} else {\n\t\targs = append(args, \"--dest-no-creds\")\n\t}\n\n\tlog.Ctx(ctx).\n\t\tTrace().\n\t\tStr(\"app\", app).\n\t\tStrs(\"args\", args).\n\t\tMsg(\"execute command to copy image\")\n\n\toutput, cmdErr := exec.CommandContext(ctx, app, args...).CombinedOutput()\n\n\t// check if the command timed out during execution for proper logging\n\tif err := ctx.Err(); err != nil {\n\t\treturn err\n\t}\n\n\t// enrich error with output from the command which may contain the actual reason\n\tif cmdErr != nil {\n\t\treturn fmt.Errorf(\"command error, stderr: %s, stdout: %s\", cmdErr.Error(), string(output))\n\t}\n\n\treturn nil\n}\n\nfunc (e *ECRClient) PullImage() error {\n\tpanic(\"implement me\")\n}\n\nfunc (e *ECRClient) PutImage() error {\n\tpanic(\"implement me\")\n}\n\nfunc (e *ECRClient) ImageExists(ctx context.Context, imageRef ctypes.ImageReference) bool {\n\tref := imageRef.DockerReference().String()\n\tif _, found := e.cache.Get(ref); found {\n\t\tlog.Ctx(ctx).Trace().Str(\"ref\", ref).Msg(\"found in cache\")\n\t\treturn true\n\t}\n\n\tapp := \"skopeo\"\n\targs := []string{\n\t\t\"inspect\",\n\t\t\"--retry-times\", \"3\",\n\t\t\"docker://\" + ref,\n\t\t\"--creds\", e.Credentials(),\n\t}\n\n\tlog.Ctx(ctx).Trace().Str(\"app\", app).Strs(\"args\", args).Msg(\"executing command to inspect image\")\n\tif err := exec.CommandContext(ctx, app, args...).Run(); err != nil {\n\t\tlog.Ctx(ctx).Trace().Str(\"ref\", ref).Msg(\"not found in target repository\")\n\t\treturn false\n\t}\n\n\tlog.Ctx(ctx).Trace().Str(\"ref\", ref).Msg(\"found in target repository\")\n\n\te.cache.SetWithTTL(ref, \"\", 1, 24*time.Hour+time.Duration(rand.Intn(180))*time.Minute)\n\n\treturn true\n}\n\nfunc (e *ECRClient) Endpoint() string {\n\treturn e.ecrDomain\n}\n\n// IsOrigin returns true if the references origin is from this registry\nfunc (e *ECRClient) IsOrigin(imageRef ctypes.ImageReference) bool {\n\tdomain := reference.Domain(imageRef.DockerReference())\n\treturn domain == e.Endpoint()\n}\n\n// requestAuthToken requests and returns an authentication token from ECR with its expiration date\nfunc (e *ECRClient) requestAuthToken() ([]byte, time.Time, error) {\n\tgetAuthTokenOutput, err := e.client.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{\n\t\tRegistryIds: []*string{&e.targetAccount},\n\t})\n\n\tif err != nil {\n\t\treturn []byte(\"\"), time.Time{}, err\n\t}\n\n\tauthToken, err := base64.StdEncoding.DecodeString(*getAuthTokenOutput.AuthorizationData[0].AuthorizationToken)\n\tif err != nil {\n\t\treturn []byte(\"\"), time.Time{}, err\n\t}\n\n\treturn authToken, *getAuthTokenOutput.AuthorizationData[0].ExpiresAt, nil\n}\n\n// scheduleTokenRenewal sets a scheduler to execute token renewal before the token expires\nfunc (e *ECRClient) scheduleTokenRenewal() error {\n\ttoken, expiryAt, err := e.requestAuthToken()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trenewalAt := expiryAt.Add(-2 * time.Minute)\n\te.authToken = token\n\n\tlog.Debug().Time(\"expiryAt\", expiryAt).Time(\"renewalAt\", renewalAt).Msg(\"auth token set, schedule next token renewal\")\n\n\tj, _ := e.scheduler.Every(1).StartAt(renewalAt).Do(e.scheduleTokenRenewal)\n\tj.LimitRunsTo(1)\n\n\treturn nil\n}\n\n// For testing purposes\nfunc NewDummyECRClient(region string, targetAccount string, role string, options config.ECROptions, authToken []byte) *ECRClient {\n\treturn &ECRClient{\n\t\ttargetAccount: targetAccount,\n\t\toptions:       options,\n\t\tecrDomain:     fmt.Sprintf(\"%s.dkr.ecr.%s.amazonaws.com\", targetAccount, region),\n\t\tauthToken:     authToken,\n\t}\n}\n\nfunc NewMockECRClient(ecrClient ecriface.ECRAPI, region string, ecrDomain string, targetAccount, role string) (*ECRClient, error) {\n\tclient := &ECRClient{\n\t\tclient:        ecrClient,\n\t\tecrDomain:     ecrDomain,\n\t\tcache:         nil,\n\t\tscheduler:     nil,\n\t\ttargetAccount: targetAccount,\n\t\tauthToken:     []byte(\"mock-ecr-client-fake-auth-token\"),\n\t\toptions: config.ECROptions{\n\t\t\tImageTagMutability:         \"MUTABLE\",\n\t\t\tImageScanningConfiguration: config.ImageScanningConfiguration{ImageScanOnPush: true},\n\t\t\tEncryptionConfiguration:    config.EncryptionConfiguration{EncryptionType: \"AES256\"},\n\t\t\tTags:                       []config.Tag{{Key: \"CreatedBy\", Value: \"k8s-image-swapper\"}, {Key: \"AnotherTag\", Value: \"another-tag\"}},\n\t\t},\n\t}\n\n\treturn client, nil\n}\n"
  },
  {
    "path": "pkg/registry/ecr_test.go",
    "content": "package registry\n\nimport (\n\t\"encoding/base64\"\n\t\"testing\"\n\n\t\"github.com/containers/image/v5/transports/alltransports\"\n\n\t\"github.com/estahn/k8s-image-swapper/pkg/config\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestDockerConfig(t *testing.T) {\n\tfakeToken := []byte(\"token\")\n\tfakeBase64Token := base64.StdEncoding.EncodeToString(fakeToken)\n\n\texpected := []byte(\"{\\\"auths\\\":{\\\"12345678912.dkr.ecr.us-east-1.amazonaws.com\\\":{\\\"auth\\\":\\\"\" + fakeBase64Token + \"\\\"}}}\")\n\n\tfakeRegistry := NewDummyECRClient(\"us-east-1\", \"12345678912\", \"\", config.ECROptions{}, fakeToken)\n\n\tr, _ := GenerateDockerConfig(fakeRegistry)\n\n\tassert.Equal(t, r, expected)\n}\n\nfunc TestECRIsOrigin(t *testing.T) {\n\ttype testCase struct {\n\t\tinput    string\n\t\texpected bool\n\t}\n\ttestcases := []testCase{\n\t\t{\n\t\t\tinput:    \"k8s.gcr.io/ingress-nginx/controller@sha256:9bba603b99bf25f6d117cf1235b6598c16033ad027b143c90fa5b3cc583c5713\",\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tinput:    \"12345678912.dkr.ecr.us-east-1.amazonaws.com/k8s.gcr.io/ingress-nginx/controller@sha256:9bba603b99bf25f6d117cf1235b6598c16033ad027b143c90fa5b3cc583c5713\",\n\t\t\texpected: true,\n\t\t},\n\t}\n\n\tfakeRegistry := NewDummyECRClient(\"us-east-1\", \"12345678912\", \"\", config.ECROptions{}, []byte(\"\"))\n\n\tfor _, testcase := range testcases {\n\t\timageRef, err := alltransports.ParseImageName(\"docker://\" + testcase.input)\n\n\t\tassert.NoError(t, err)\n\n\t\tresult := fakeRegistry.IsOrigin(imageRef)\n\n\t\tassert.Equal(t, testcase.expected, result)\n\t}\n}\n"
  },
  {
    "path": "pkg/registry/gar.go",
    "content": "package registry\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"math/rand\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"time\"\n\n\tartifactregistry \"cloud.google.com/go/artifactregistry/apiv1\"\n\t\"github.com/containers/image/v5/docker/reference\"\n\tctypes \"github.com/containers/image/v5/types\"\n\t\"github.com/dgraph-io/ristretto\"\n\t\"github.com/estahn/k8s-image-swapper/pkg/config\"\n\t\"github.com/go-co-op/gocron\"\n\t\"google.golang.org/api/option\"\n\t\"google.golang.org/api/transport\"\n\n\t\"github.com/rs/zerolog/log\"\n)\n\ntype GARAPI interface{}\n\ntype GARClient struct {\n\tclient    GARAPI\n\tgarDomain string\n\tcache     *ristretto.Cache\n\tscheduler *gocron.Scheduler\n\tauthToken []byte\n}\n\nfunc NewGARClient(clientConfig config.GCP) (*GARClient, error) {\n\tcache, err := ristretto.NewCache(&ristretto.Config{\n\t\tNumCounters: 1e7,     // number of keys to track frequency of (10M).\n\t\tMaxCost:     1 << 30, // maximum cost of cache (1GB).\n\t\tBufferItems: 64,      // number of keys per Get buffer.\n\t})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tscheduler := gocron.NewScheduler(time.UTC)\n\tscheduler.StartAsync()\n\n\tclient := &GARClient{\n\t\tclient:    nil,\n\t\tgarDomain: clientConfig.GarDomain(),\n\t\tcache:     cache,\n\t\tscheduler: scheduler,\n\t}\n\n\tif err := client.scheduleTokenRenewal(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn client, nil\n}\n\n// CreateRepository is empty since repositories are not created for artifact registry\nfunc (e *GARClient) CreateRepository(ctx context.Context, name string) error {\n\treturn nil\n}\n\nfunc (e *GARClient) RepositoryExists() bool {\n\tpanic(\"implement me\")\n}\n\nfunc (e *GARClient) CopyImage(ctx context.Context, srcRef ctypes.ImageReference, srcCreds string, destRef ctypes.ImageReference, destCreds string) error {\n\tsrc := srcRef.DockerReference().String()\n\tdest := destRef.DockerReference().String()\n\n\tcreds := []string{\"--src-authfile\", srcCreds}\n\n\t// use client credentials for any source GAR repositories\n\tif strings.HasSuffix(reference.Domain(srcRef.DockerReference()), \"-docker.pkg.dev\") {\n\t\tcreds = []string{\"--src-creds\", e.Credentials()}\n\t}\n\n\tapp := \"skopeo\"\n\targs := []string{\n\t\t\"--override-os\", \"linux\",\n\t\t\"copy\",\n\t\t\"--multi-arch\", \"all\",\n\t\t\"--retry-times\", \"3\",\n\t\t\"docker://\" + src,\n\t\t\"docker://\" + dest,\n\t}\n\n\tif len(creds[1]) > 0 {\n\t\targs = append(args, creds...)\n\t} else {\n\t\targs = append(args, \"--src-no-creds\")\n\t}\n\n\tif len(destCreds) > 0 {\n\t\targs = append(args, \"--dest-creds\", destCreds)\n\t} else {\n\t\targs = append(args, \"--dest-no-creds\")\n\t}\n\n\tlog.Ctx(ctx).\n\t\tTrace().\n\t\tStr(\"app\", app).\n\t\tStrs(\"args\", args).\n\t\tMsg(\"execute command to copy image\")\n\n\toutput, cmdErr := exec.CommandContext(ctx, app, args...).CombinedOutput()\n\n\t// check if the command timed out during execution for proper logging\n\tif err := ctx.Err(); err != nil {\n\t\treturn err\n\t}\n\n\t// enrich error with output from the command which may contain the actual reason\n\tif cmdErr != nil {\n\t\treturn fmt.Errorf(\"command error, stderr: %s, stdout: %s\", cmdErr.Error(), string(output))\n\t}\n\n\treturn nil\n}\n\nfunc (e *GARClient) PullImage() error {\n\tpanic(\"implement me\")\n}\n\nfunc (e *GARClient) PutImage() error {\n\tpanic(\"implement me\")\n}\n\nfunc (e *GARClient) ImageExists(ctx context.Context, imageRef ctypes.ImageReference) bool {\n\tref := imageRef.DockerReference().String()\n\tif _, found := e.cache.Get(ref); found {\n\t\tlog.Ctx(ctx).Trace().Str(\"ref\", ref).Msg(\"found in cache\")\n\t\treturn true\n\t}\n\n\tapp := \"skopeo\"\n\targs := []string{\n\t\t\"inspect\",\n\t\t\"--retry-times\", \"3\",\n\t\t\"docker://\" + ref,\n\t\t\"--creds\", e.Credentials(),\n\t}\n\n\tlog.Ctx(ctx).Trace().Str(\"app\", app).Strs(\"args\", args).Msg(\"executing command to inspect image\")\n\tif err := exec.CommandContext(ctx, app, args...).Run(); err != nil {\n\t\tlog.Trace().Str(\"ref\", ref).Msg(\"not found in target repository\")\n\t\treturn false\n\t}\n\n\tlog.Ctx(ctx).Trace().Str(\"ref\", ref).Msg(\"found in target repository\")\n\n\te.cache.SetWithTTL(ref, \"\", 1, 24*time.Hour+time.Duration(rand.Intn(180))*time.Minute)\n\n\treturn true\n}\n\nfunc (e *GARClient) Endpoint() string {\n\treturn e.garDomain\n}\n\n// IsOrigin returns true if the references origin is from this registry\nfunc (e *GARClient) IsOrigin(imageRef ctypes.ImageReference) bool {\n\treturn strings.HasPrefix(imageRef.DockerReference().String(), e.Endpoint())\n}\n\n// requestAuthToken requests and returns an authentication token from GAR with its expiration date\nfunc (e *GARClient) requestAuthToken() ([]byte, time.Time, error) {\n\tctx := context.Background()\n\tcreds, err := transport.Creds(ctx, option.WithScopes(artifactregistry.DefaultAuthScopes()...))\n\tif err != nil {\n\t\tlog.Err(err).Msg(\"generating gcp creds\")\n\t\treturn []byte(\"\"), time.Time{}, err\n\t}\n\ttoken, err := creds.TokenSource.Token()\n\tif err != nil {\n\t\tlog.Err(err).Msg(\"generating token\")\n\t\treturn []byte(\"\"), time.Time{}, err\n\t}\n\n\treturn []byte(fmt.Sprintf(\"oauth2accesstoken:%v\", token.AccessToken)), token.Expiry, nil\n}\n\n// scheduleTokenRenewal sets a scheduler to execute token renewal before the token expires\nfunc (e *GARClient) scheduleTokenRenewal() error {\n\ttoken, expiryAt, err := e.requestAuthToken()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\trenewalAt := expiryAt.Add(-2 * time.Minute)\n\te.authToken = token\n\n\tlog.Debug().Time(\"expiryAt\", expiryAt).Time(\"renewalAt\", renewalAt).Msg(\"auth token set, schedule next token renewal\")\n\n\tj, _ := e.scheduler.Every(1).StartAt(renewalAt).Do(e.scheduleTokenRenewal)\n\tj.LimitRunsTo(1)\n\n\treturn nil\n}\n\nfunc (e *GARClient) Credentials() string {\n\treturn string(e.authToken)\n}\n\nfunc (e *GARClient) DockerConfig() ([]byte, error) {\n\tdockerConfig := DockerConfig{\n\t\tAuthConfigs: map[string]AuthConfig{\n\t\t\te.garDomain: {\n\t\t\t\tAuth: base64.StdEncoding.EncodeToString(e.authToken),\n\t\t\t},\n\t\t},\n\t}\n\n\tdockerConfigJson, err := json.Marshal(dockerConfig)\n\tif err != nil {\n\t\treturn []byte{}, err\n\t}\n\n\treturn dockerConfigJson, nil\n}\n\nfunc NewMockGARClient(garClient GARAPI, garDomain string) (*GARClient, error) {\n\tclient := &GARClient{\n\t\tclient:    garClient,\n\t\tgarDomain: garDomain,\n\t\tcache:     nil,\n\t\tscheduler: nil,\n\t\tauthToken: []byte(\"oauth2accesstoken:mock-gar-client-fake-auth-token\"),\n\t}\n\n\treturn client, nil\n}\n"
  },
  {
    "path": "pkg/registry/gar_test.go",
    "content": "package registry\n\nimport (\n\t\"testing\"\n\n\t\"github.com/containers/image/v5/transports/alltransports\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestGARIsOrigin(t *testing.T) {\n\ttype testCase struct {\n\t\tinput    string\n\t\texpected bool\n\t}\n\ttestcases := []testCase{\n\t\t{\n\t\t\tinput:    \"k8s.gcr.io/ingress-nginx/controller@sha256:9bba603b99bf25f6d117cf1235b6598c16033ad027b143c90fa5b3cc583c5713\",\n\t\t\texpected: false,\n\t\t},\n\t\t{\n\t\t\tinput:    \"us-central1-docker.pkg.dev/gcp-project-123/main/k8s.gcr.io/ingress-nginx/controller@sha256:9bba603b99bf25f6d117cf1235b6598c16033ad027b143c90fa5b3cc583c5713\",\n\t\t\texpected: true,\n\t\t},\n\t}\n\n\tfakeRegistry, _ := NewMockGARClient(nil, \"us-central1-docker.pkg.dev/gcp-project-123/main\")\n\n\tfor _, testcase := range testcases {\n\t\timageRef, err := alltransports.ParseImageName(\"docker://\" + testcase.input)\n\n\t\tassert.NoError(t, err)\n\n\t\tresult := fakeRegistry.IsOrigin(imageRef)\n\n\t\tassert.Equal(t, testcase.expected, result)\n\t}\n}\n"
  },
  {
    "path": "pkg/registry/inmemory.go",
    "content": "package registry\n"
  },
  {
    "path": "pkg/secrets/dummy.go",
    "content": "package secrets\n\nimport (\n\t\"context\"\n\n\t\"github.com/estahn/k8s-image-swapper/pkg/registry\"\n\tv1 \"k8s.io/api/core/v1\"\n)\n\n// DummyImagePullSecretsProvider does nothing\ntype DummyImagePullSecretsProvider struct {\n}\n\n// NewDummyImagePullSecretsProvider initialises a dummy image pull secrets provider\nfunc NewDummyImagePullSecretsProvider() ImagePullSecretsProvider {\n\treturn &DummyImagePullSecretsProvider{}\n}\n\nfunc (p *DummyImagePullSecretsProvider) SetAuthenticatedRegistries(registries []registry.Client) {\n}\n\n// GetImagePullSecrets returns an empty ImagePullSecretsResult\nfunc (p *DummyImagePullSecretsProvider) GetImagePullSecrets(ctx context.Context, pod *v1.Pod) (*ImagePullSecretsResult, error) {\n\treturn NewImagePullSecretsResult(), nil\n}\n"
  },
  {
    "path": "pkg/secrets/dummy_test.go",
    "content": "package secrets\n\nimport (\n\t\"context\"\n\t\"reflect\"\n\t\"testing\"\n\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nfunc TestDummyImagePullSecretsProvider_GetImagePullSecrets(t *testing.T) {\n\ttype args struct {\n\t\tpod *corev1.Pod\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twant    *ImagePullSecretsResult\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"default\",\n\t\t\targs: args{\n\t\t\t\tpod: &corev1.Pod{\n\t\t\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\t\t\tNamespace: \"test-ns\",\n\t\t\t\t\t\tName:      \"my-pod\",\n\t\t\t\t\t},\n\t\t\t\t\tSpec: corev1.PodSpec{\n\t\t\t\t\t\tServiceAccountName: \"my-service-account\",\n\t\t\t\t\t\tImagePullSecrets: []corev1.LocalObjectReference{\n\t\t\t\t\t\t\t{Name: \"my-pod-secret\"},\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twant:    NewImagePullSecretsResult(),\n\t\t\twantErr: false,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tp := &DummyImagePullSecretsProvider{}\n\t\t\tgot, err := p.GetImagePullSecrets(context.Background(), tt.args.pod)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"GetImagePullSecrets() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"GetImagePullSecrets() got = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestNewDummyImagePullSecretsProvider(t *testing.T) {\n\ttests := []struct {\n\t\tname string\n\t\twant ImagePullSecretsProvider\n\t}{\n\t\t{\n\t\t\tname: \"default\",\n\t\t\twant: &DummyImagePullSecretsProvider{},\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tif got := NewDummyImagePullSecretsProvider(); !reflect.DeepEqual(got, tt.want) {\n\t\t\t\tt.Errorf(\"NewDummyImagePullSecretsProvider() = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/secrets/kubernetes.go",
    "content": "package secrets\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"github.com/estahn/k8s-image-swapper/pkg/registry\"\n\tjsonpatch \"github.com/evanphx/json-patch\"\n\t\"github.com/rs/zerolog/log\"\n\tv1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes\"\n)\n\n// KubernetesImagePullSecretsProvider retrieves the secrets holding docker auth information from Kubernetes and merges\n// them if necessary. Supports Pod secrets as well as ServiceAccount secrets.\ntype KubernetesImagePullSecretsProvider struct {\n\tkubernetesClient        kubernetes.Interface\n\tauthenticatedRegistries []registry.Client\n}\n\n// ImagePullSecretsResult contains the result of GetImagePullSecrets\ntype ImagePullSecretsResult struct {\n\tSecrets   map[string][]byte\n\tAggregate []byte\n}\n\n// NewImagePullSecretsResult initialises ImagePullSecretsResult\nfunc NewImagePullSecretsResult() *ImagePullSecretsResult {\n\treturn &ImagePullSecretsResult{\n\t\tSecrets:   map[string][]byte{},\n\t\tAggregate: []byte(\"{}\"),\n\t}\n}\n\n// Initialiaze an ImagePullSecretsResult and registers image pull secrets from the given registries\nfunc NewImagePullSecretsResultWithDefaults(defaultImagePullSecrets []registry.Client) *ImagePullSecretsResult {\n\timagePullSecretsResult := NewImagePullSecretsResult()\n\tfor index, reg := range defaultImagePullSecrets {\n\t\tdockerConfig, err := registry.GenerateDockerConfig(reg)\n\t\tif err != nil {\n\t\t\tlog.Err(err)\n\t\t} else {\n\t\t\timagePullSecretsResult.Add(fmt.Sprintf(\"source-ecr-%d\", index), dockerConfig)\n\t\t}\n\t}\n\treturn imagePullSecretsResult\n}\n\n// Add a secrets to internal list and rebuilds the aggregate\nfunc (r *ImagePullSecretsResult) Add(name string, data []byte) {\n\tr.Secrets[name] = data\n\tr.Aggregate, _ = jsonpatch.MergePatch(r.Aggregate, data)\n}\n\n// AuthFile provides the aggregate as a file to be used by a docker client\nfunc (r *ImagePullSecretsResult) AuthFile() (*os.File, error) {\n\ttmpfile, err := os.CreateTemp(\"\", \"auth\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif _, err := tmpfile.Write(r.Aggregate); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := tmpfile.Close(); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn tmpfile, nil\n}\n\nfunc NewKubernetesImagePullSecretsProvider(clientset kubernetes.Interface) ImagePullSecretsProvider {\n\treturn &KubernetesImagePullSecretsProvider{\n\t\tkubernetesClient:        clientset,\n\t\tauthenticatedRegistries: []registry.Client{},\n\t}\n}\n\nfunc (p *KubernetesImagePullSecretsProvider) SetAuthenticatedRegistries(registries []registry.Client) {\n\tp.authenticatedRegistries = registries\n}\n\n// GetImagePullSecrets returns all secrets with their respective content\nfunc (p *KubernetesImagePullSecretsProvider) GetImagePullSecrets(ctx context.Context, pod *v1.Pod) (*ImagePullSecretsResult, error) {\n\tvar secrets = make(map[string][]byte)\n\n\timagePullSecrets := pod.Spec.ImagePullSecrets\n\n\t// retrieve secret names from pod ServiceAccount (spec.imagePullSecrets)\n\tserviceAccount, err := p.kubernetesClient.CoreV1().\n\t\tServiceAccounts(pod.Namespace).\n\t\tGet(ctx, pod.Spec.ServiceAccountName, metav1.GetOptions{})\n\tif err != nil {\n\t\tlog.Ctx(ctx).Warn().Msg(\"error fetching referenced service account, continue without service account imagePullSecrets\")\n\t}\n\n\tif serviceAccount != nil {\n\t\timagePullSecrets = append(imagePullSecrets, serviceAccount.ImagePullSecrets...)\n\t}\n\n\tresult := NewImagePullSecretsResultWithDefaults(p.authenticatedRegistries)\n\tfor _, imagePullSecret := range imagePullSecrets {\n\t\t// fetch a secret only once\n\t\tif _, exists := secrets[imagePullSecret.Name]; exists {\n\t\t\tcontinue\n\t\t}\n\n\t\tsecret, err := p.kubernetesClient.CoreV1().Secrets(pod.Namespace).Get(ctx, imagePullSecret.Name, metav1.GetOptions{})\n\t\tif err != nil {\n\t\t\tlog.Ctx(ctx).Err(err).Msg(\"error fetching secret, continue without imagePullSecrets\")\n\t\t}\n\n\t\tif secret == nil || secret.Type != v1.SecretTypeDockerConfigJson {\n\t\t\tcontinue\n\t\t}\n\n\t\tresult.Add(imagePullSecret.Name, secret.Data[v1.DockerConfigJsonKey])\n\t}\n\n\treturn result, nil\n}\n"
  },
  {
    "path": "pkg/secrets/kubernetes_test.go",
    "content": "package secrets\n\nimport (\n\t\"context\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/estahn/k8s-image-swapper/pkg/config\"\n\t\"github.com/estahn/k8s-image-swapper/pkg/registry\"\n\t\"github.com/stretchr/testify/assert\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\n//type ExampleTestSuite struct {\n//\tsuite.Suite\n//}\n//\n//func (suite *ExampleTestSuite) SetupTest() {\n//}\n//func (suite *ExampleTestSuite) TestExample() {\n//\tassert.Equal(suite.T(), 5, 1)\n//}\n//\n//func TestExampleTestSuite(t *testing.T) {\n//\tsuite.Run(t, new(ExampleTestSuite))\n//}\n\n// Test:\n//+------------------+-----+----------------+\n//|                  | Pod | ServiceAccount |\n//+------------------+-----+----------------+\n//| ImagePullSecrets | Y   | Y              |\n//+------------------+-----+----------------+\n//| ImagePullSecrets | Y   | N              |\n//+------------------+-----+----------------+\n//| ImagePullSecrets | N   | Y              |\n//+------------------+-----+----------------+\n//| ImagePullSecrets | N   | N              |\n//+------------------+-----+----------------+\n//\n// Multple image pull secrets on pod + service account\n// Pod secret should override service account secret\n\nfunc TestKubernetesCredentialProvider_GetImagePullSecrets(t *testing.T) {\n\tclientSet := fake.NewSimpleClientset()\n\n\tsvcAccount := &corev1.ServiceAccount{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: \"my-service-account\",\n\t\t},\n\t\tImagePullSecrets: []corev1.LocalObjectReference{\n\t\t\t{Name: \"my-sa-secret\"},\n\t\t},\n\t}\n\tsvcAccountSecretDockerConfigJson := []byte(`{\"auths\":{\"my-sa-secret.registry.example.com\":{\"username\":\"my-sa-secret\",\"password\":\"xxxxxxxxxxx\",\"email\":\"jdoe@example.com\",\"auth\":\"c3R...zE2\"}}}`)\n\tsvcAccountSecret := &corev1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: \"my-sa-secret\",\n\t\t},\n\t\tType: corev1.SecretTypeDockerConfigJson,\n\t\tData: map[string][]byte{\n\t\t\tcorev1.DockerConfigJsonKey: svcAccountSecretDockerConfigJson,\n\t\t},\n\t}\n\tpodSecretDockerConfigJson := []byte(`{\"auths\":{\"my-pod-secret.registry.example.com\":{\"username\":\"my-sa-secret\",\"password\":\"xxxxxxxxxxx\",\"email\":\"jdoe@example.com\",\"auth\":\"c3R...zE2\"}}}`)\n\tpodSecret := &corev1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: \"my-pod-secret\",\n\t\t},\n\t\tType: corev1.SecretTypeDockerConfigJson,\n\t\tData: map[string][]byte{\n\t\t\tcorev1.DockerConfigJsonKey: podSecretDockerConfigJson,\n\t\t},\n\t}\n\tpod := &corev1.Pod{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tNamespace: \"test-ns\",\n\t\t\tName:      \"my-pod\",\n\t\t},\n\t\tSpec: corev1.PodSpec{\n\t\t\tServiceAccountName: \"my-service-account\",\n\t\t\tImagePullSecrets: []corev1.LocalObjectReference{\n\t\t\t\t{Name: \"my-pod-secret\"},\n\t\t\t},\n\t\t},\n\t}\n\n\t_, _ = clientSet.CoreV1().ServiceAccounts(\"test-ns\").Create(context.TODO(), svcAccount, metav1.CreateOptions{})\n\t_, _ = clientSet.CoreV1().Secrets(\"test-ns\").Create(context.TODO(), svcAccountSecret, metav1.CreateOptions{})\n\t_, _ = clientSet.CoreV1().Secrets(\"test-ns\").Create(context.TODO(), podSecret, metav1.CreateOptions{})\n\n\tprovider := NewKubernetesImagePullSecretsProvider(clientSet)\n\tresult, err := provider.GetImagePullSecrets(context.Background(), pod)\n\n\tassert.NoError(t, err)\n\tassert.NotNil(t, result)\n\tassert.Len(t, result.Secrets, 2)\n\tassert.Equal(t, svcAccountSecretDockerConfigJson, result.Secrets[\"my-sa-secret\"])\n\tassert.Equal(t, podSecretDockerConfigJson, result.Secrets[\"my-pod-secret\"])\n}\n\n// TestImagePullSecretsResult_WithDefault tests if authenticated private registries work\nfunc TestImagePullSecretsResult_WithDefault(t *testing.T) {\n\tfakeToken := []byte(\"token\")\n\tfakeBase64Token := base64.StdEncoding.EncodeToString(fakeToken)\n\tfakeAccountIds := []string{\"12345678912\", \"9876543210\"}\n\tfakeRegions := []string{\"us-east-1\", \"us-west-1\"}\n\tfakeEcrDomains := []string{\n\t\tfmt.Sprintf(\"%s.dkr.ecr.%s.amazonaws.com\", fakeAccountIds[0], fakeRegions[0]),\n\t\tfmt.Sprintf(\"%s.dkr.ecr.%s.amazonaws.com\", fakeAccountIds[1], fakeRegions[1]),\n\t}\n\n\texpected := &ImagePullSecretsResult{\n\t\tSecrets: map[string][]byte{\n\t\t\t\"source-ecr-0\": []byte(\"{\\\"auths\\\":{\\\"\" + fakeEcrDomains[0] + \"\\\":{\\\"auth\\\":\\\"\" + fakeBase64Token + \"\\\"}}}\"),\n\t\t\t\"source-ecr-1\": []byte(\"{\\\"auths\\\":{\\\"\" + fakeEcrDomains[1] + \"\\\":{\\\"auth\\\":\\\"\" + fakeBase64Token + \"\\\"}}}\"),\n\t\t},\n\t\tAggregate: []byte(\"{\\\"auths\\\":{\\\"\" + fakeEcrDomains[0] + \"\\\":{\\\"auth\\\":\\\"\" + fakeBase64Token + \"\\\"},\\\"\" + fakeEcrDomains[1] + \"\\\":{\\\"auth\\\":\\\"\" + fakeBase64Token + \"\\\"}}}\"),\n\t}\n\n\tfakeRegistry1 := registry.NewDummyECRClient(fakeRegions[0], fakeAccountIds[0], \"\", config.ECROptions{}, fakeToken)\n\tfakeRegistry2 := registry.NewDummyECRClient(fakeRegions[1], fakeAccountIds[1], \"\", config.ECROptions{}, fakeToken)\n\tfakeRegistries := []registry.Client{fakeRegistry1, fakeRegistry2}\n\n\tr := NewImagePullSecretsResultWithDefaults(fakeRegistries)\n\n\tassert.Equal(t, r, expected)\n}\n\n// TestImagePullSecretsResult_Add tests if aggregation works\nfunc TestImagePullSecretsResult_Add(t *testing.T) {\n\texpected := &ImagePullSecretsResult{\n\t\tSecrets: map[string][]byte{\n\t\t\t\"foo\": []byte(\"{\\\"foo\\\":\\\"123\\\"}\"),\n\t\t\t\"bar\": []byte(\"{\\\"bar\\\":\\\"456\\\"}\"),\n\t\t},\n\t\tAggregate: []byte(\"{\\\"bar\\\":\\\"456\\\",\\\"foo\\\":\\\"123\\\"}\"),\n\t}\n\n\tr := NewImagePullSecretsResult()\n\tr.Add(\"foo\", []byte(\"{\\\"foo\\\":\\\"123\\\"}\"))\n\tr.Add(\"bar\", []byte(\"{\\\"bar\\\":\\\"456\\\"}\"))\n\n\tassert.Equal(t, r, expected)\n}\n"
  },
  {
    "path": "pkg/secrets/provider.go",
    "content": "package secrets\n\nimport (\n\t\"context\"\n\n\t\"github.com/estahn/k8s-image-swapper/pkg/registry\"\n\tv1 \"k8s.io/api/core/v1\"\n)\n\ntype ImagePullSecretsProvider interface {\n\tGetImagePullSecrets(ctx context.Context, pod *v1.Pod) (*ImagePullSecretsResult, error)\n\tSetAuthenticatedRegistries(privateRegistries []registry.Client)\n}\n"
  },
  {
    "path": "pkg/types/types.go",
    "content": "package types\n\nimport \"fmt\"\n\ntype Registry int\n\nconst (\n\tRegistryUnknown = iota\n\tRegistryAWS\n\tRegistryGCP\n)\n\nfunc (p Registry) String() string {\n\treturn [...]string{\"unknown\", \"aws\", \"gcp\"}[p]\n}\n\nfunc ParseRegistry(p string) (Registry, error) {\n\tswitch p {\n\tcase Registry(RegistryAWS).String():\n\t\treturn RegistryAWS, nil\n\tcase Registry(RegistryGCP).String():\n\t\treturn RegistryGCP, nil\n\t}\n\treturn RegistryUnknown, fmt.Errorf(\"unknown target registry string: '%s', defaulting to unknown\", p)\n}\n\ntype ImageSwapPolicy int\n\nconst (\n\tImageSwapPolicyAlways = iota\n\tImageSwapPolicyExists\n)\n\nfunc (p ImageSwapPolicy) String() string {\n\treturn [...]string{\"always\", \"exists\"}[p]\n}\n\nfunc ParseImageSwapPolicy(p string) (ImageSwapPolicy, error) {\n\tswitch p {\n\tcase ImageSwapPolicy(ImageSwapPolicyAlways).String():\n\t\treturn ImageSwapPolicyAlways, nil\n\tcase ImageSwapPolicy(ImageSwapPolicyExists).String():\n\t\treturn ImageSwapPolicyExists, nil\n\t}\n\treturn ImageSwapPolicyExists, fmt.Errorf(\"unknown image swap policy string: '%s', defaulting to exists\", p)\n}\n\ntype ImageCopyPolicy int\n\nconst (\n\tImageCopyPolicyDelayed = iota\n\tImageCopyPolicyImmediate\n\tImageCopyPolicyForce\n\tImageCopyPolicyNone\n)\n\nfunc (p ImageCopyPolicy) String() string {\n\treturn [...]string{\"delayed\", \"immediate\", \"force\", \"none\"}[p]\n}\n\nfunc ParseImageCopyPolicy(p string) (ImageCopyPolicy, error) {\n\tswitch p {\n\tcase ImageCopyPolicy(ImageCopyPolicyDelayed).String():\n\t\treturn ImageCopyPolicyDelayed, nil\n\tcase ImageCopyPolicy(ImageCopyPolicyImmediate).String():\n\t\treturn ImageCopyPolicyImmediate, nil\n\tcase ImageCopyPolicy(ImageCopyPolicyForce).String():\n\t\treturn ImageCopyPolicyForce, nil\n\tcase ImageCopyPolicy(ImageCopyPolicyNone).String():\n\t\treturn ImageCopyPolicyNone, nil\n\t}\n\treturn ImageCopyPolicyDelayed, fmt.Errorf(\"unknown image copy policy string: '%s', defaulting to delayed\", p)\n}\n"
  },
  {
    "path": "pkg/types/types_test.go",
    "content": "package types\n\nimport \"testing\"\n\nfunc TestParseImageSwapPolicy(t *testing.T) {\n\ttype args struct {\n\t\tp string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twant    ImageSwapPolicy\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"always\",\n\t\t\targs: args{p: \"always\"},\n\t\t\twant: ImageSwapPolicyAlways,\n\t\t},\n\t\t{\n\t\t\tname: \"exists\",\n\t\t\targs: args{p: \"exists\"},\n\t\t\twant: ImageSwapPolicyExists,\n\t\t},\n\t\t{\n\t\t\tname:    \"random-non-existent\",\n\t\t\targs:    args{p: \"random-non-existent\"},\n\t\t\twant:    ImageSwapPolicyExists,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := ParseImageSwapPolicy(tt.args.p)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ParseImageSwapPolicy() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif got != tt.want {\n\t\t\t\tt.Errorf(\"ParseImageSwapPolicy() got = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestParseImageCopyPolicy(t *testing.T) {\n\ttype args struct {\n\t\tp string\n\t}\n\ttests := []struct {\n\t\tname    string\n\t\targs    args\n\t\twant    ImageCopyPolicy\n\t\twantErr bool\n\t}{\n\t\t{\n\t\t\tname: \"delayed\",\n\t\t\targs: args{p: \"delayed\"},\n\t\t\twant: ImageCopyPolicyDelayed,\n\t\t},\n\t\t{\n\t\t\tname: \"immediate\",\n\t\t\targs: args{p: \"immediate\"},\n\t\t\twant: ImageCopyPolicyImmediate,\n\t\t},\n\t\t{\n\t\t\tname: \"force\",\n\t\t\targs: args{p: \"force\"},\n\t\t\twant: ImageCopyPolicyForce,\n\t\t},\n\t\t{\n\t\t\tname: \"none\",\n\t\t\targs: args{p: \"none\"},\n\t\t\twant: ImageCopyPolicyNone,\n\t\t},\n\t\t{\n\t\t\tname:    \"random-non-existent\",\n\t\t\targs:    args{p: \"random-non-existent\"},\n\t\t\twant:    ImageCopyPolicyDelayed,\n\t\t\twantErr: true,\n\t\t},\n\t}\n\tfor _, tt := range tests {\n\t\tt.Run(tt.name, func(t *testing.T) {\n\t\t\tgot, err := ParseImageCopyPolicy(tt.args.p)\n\t\t\tif (err != nil) != tt.wantErr {\n\t\t\t\tt.Errorf(\"ParseImageCopyPolicy() error = %v, wantErr %v\", err, tt.wantErr)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif got != tt.want {\n\t\t\t\tt.Errorf(\"ParseImageCopyPolicy() got = %v, want %v\", got, tt.want)\n\t\t\t}\n\t\t})\n\t}\n}\n"
  },
  {
    "path": "pkg/webhook/image_copier.go",
    "content": "package webhook\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"os\"\n\n\t\"github.com/containers/image/v5/docker/reference\"\n\tctypes \"github.com/containers/image/v5/types\"\n\t\"github.com/rs/zerolog/log\"\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\n// struct representing a job of copying an image with its subcontext\ntype ImageCopier struct {\n\tsourcePod      *corev1.Pod\n\tsourceImageRef ctypes.ImageReference\n\ttargetImageRef ctypes.ImageReference\n\n\timagePullPolicy corev1.PullPolicy\n\timageSwapper    *ImageSwapper\n\n\tcontext       context.Context\n\tcancelContext context.CancelFunc\n}\n\ntype Task struct {\n\tfunction    func() error\n\tdescription string\n}\n\nvar ErrImageAlreadyPresent = errors.New(\"image already present in target registry\")\n\n// replace the default context with a new one with a timeout\nfunc (ic *ImageCopier) withDeadline() *ImageCopier {\n\timageCopierContext, imageCopierContextCancel := context.WithTimeout(ic.context, ic.imageSwapper.imageCopyDeadline)\n\tic.context = imageCopierContext\n\tic.cancelContext = imageCopierContextCancel\n\treturn ic\n}\n\n// start the image copy job\nfunc (ic *ImageCopier) start() {\n\tif _, hasDeadline := ic.context.Deadline(); hasDeadline {\n\t\tdefer ic.cancelContext()\n\t}\n\n\t// list of actions to execute in order to copy an image\n\ttasks := []*Task{\n\t\t{\n\t\t\tfunction:    ic.taskCheckImage,\n\t\t\tdescription: \"checking image presence in target registry\",\n\t\t},\n\t\t{\n\t\t\tfunction:    ic.taskCreateRepository,\n\t\t\tdescription: \"creating a new repository in target registry\",\n\t\t},\n\t\t{\n\t\t\tfunction:    ic.taskCopyImage,\n\t\t\tdescription: \"copying image data to target repository\",\n\t\t},\n\t}\n\n\tfor _, task := range tasks {\n\t\terr := ic.run(task.function)\n\n\t\tif err != nil {\n\t\t\tif errors.Is(err, context.DeadlineExceeded) {\n\t\t\t\tlog.Ctx(ic.context).Err(err).Msg(\"timeout during image copy\")\n\t\t\t} else if errors.Is(err, ErrImageAlreadyPresent) {\n\t\t\t\tlog.Ctx(ic.context).Trace().Msgf(\"image copy aborted: %s\", err.Error())\n\t\t\t} else {\n\t\t\t\tlog.Ctx(ic.context).Err(err).Msgf(\"image copy error while %s\", task.description)\n\t\t\t}\n\t\t\tbreak\n\t\t}\n\t}\n}\n\n// run a task function and check for timeout\nfunc (ic *ImageCopier) run(taskFunc func() error) error {\n\tif err := ic.context.Err(); err != nil {\n\t\treturn err\n\t}\n\n\treturn taskFunc()\n}\n\nfunc (ic *ImageCopier) taskCheckImage() error {\n\tregistryClient := ic.imageSwapper.registryClient\n\n\timageAlreadyExists := registryClient.ImageExists(ic.context, ic.targetImageRef) && ic.imagePullPolicy != corev1.PullAlways\n\n\tif err := ic.context.Err(); err != nil {\n\t\treturn err\n\t} else if imageAlreadyExists {\n\t\treturn ErrImageAlreadyPresent\n\t}\n\n\treturn nil\n}\n\nfunc (ic *ImageCopier) taskCreateRepository() error {\n\tcreateRepoName := reference.TrimNamed(ic.sourceImageRef.DockerReference()).String()\n\n\treturn ic.imageSwapper.registryClient.CreateRepository(ic.context, createRepoName)\n}\n\nfunc (ic *ImageCopier) taskCopyImage() error {\n\tctx := ic.context\n\t// Retrieve secrets and auth credentials\n\timagePullSecrets, err := ic.imageSwapper.imagePullSecretProvider.GetImagePullSecrets(ctx, ic.sourcePod)\n\t// not possible at the moment\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tauthFile, err := imagePullSecrets.AuthFile()\n\tif err != nil {\n\t\tlog.Ctx(ctx).Err(err).Msg(\"failed generating authFile\")\n\t}\n\n\tdefer func() {\n\t\tif err := os.RemoveAll(authFile.Name()); err != nil {\n\t\t\tlog.Ctx(ctx).Err(err).Str(\"file\", authFile.Name()).Msg(\"failed removing auth file\")\n\t\t}\n\t}()\n\n\tif err := ctx.Err(); err != nil {\n\t\treturn err\n\t}\n\n\t// Copy image\n\t// TODO: refactor to use structure instead of passing file name / string\n\t//\n\t//\tor transform registryClient creds into auth compatible form, e.g.\n\t//\t{\"auths\":{\"aws_account_id.dkr.ecr.region.amazonaws.com\":{\"username\":\"AWS\",\"password\":\"...\"\t}}}\n\treturn ic.imageSwapper.registryClient.CopyImage(ctx, ic.sourceImageRef, authFile.Name(), ic.targetImageRef, ic.imageSwapper.registryClient.Credentials())\n}\n"
  },
  {
    "path": "pkg/webhook/image_copier_test.go",
    "content": "package webhook\n\nimport (\n\t\"context\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/service/ecr\"\n\t\"github.com/containers/image/v5/transports/alltransports\"\n\t\"github.com/estahn/k8s-image-swapper/pkg/registry\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\tcorev1 \"k8s.io/api/core/v1\"\n)\n\nfunc TestImageCopier_withDeadline(t *testing.T) {\n\tmutator := NewImageSwapperWithOpts(\n\t\tnil,\n\t\tImageCopyDeadline(8*time.Second),\n\t)\n\n\timageSwapper, _ := mutator.(*ImageSwapper)\n\n\timageCopier := &ImageCopier{\n\t\timageSwapper: imageSwapper,\n\t\tcontext:      context.Background(),\n\t}\n\n\timageCopier = imageCopier.withDeadline()\n\tdeadline, hasDeadline := imageCopier.context.Deadline()\n\n\t// test that a deadline has been set\n\tassert.Equal(t, true, hasDeadline)\n\n\t// test that the deadline is future\n\tassert.GreaterOrEqual(t, deadline, time.Now())\n\n\t// test that the context can be canceled\n\tassert.NotEqual(t, nil, imageCopier.context.Done())\n\n\timageCopier.cancelContext()\n\n\t_, ok := <-imageCopier.context.Done()\n\t// test that the Done channel is closed, meaning the context is canceled\n\tassert.Equal(t, false, ok)\n\n}\n\nfunc TestImageCopier_tasksTimeout(t *testing.T) {\n\tecrClient := new(mockECRClient)\n\tecrClient.On(\n\t\t\"CreateRepositoryWithContext\",\n\t\tmock.AnythingOfType(\"*context.timerCtx\"),\n\t\t&ecr.CreateRepositoryInput{\n\t\t\tImageScanningConfiguration: &ecr.ImageScanningConfiguration{\n\t\t\t\tScanOnPush: aws.Bool(true),\n\t\t\t},\n\t\t\tImageTagMutability: aws.String(\"MUTABLE\"),\n\t\t\tRepositoryName:     aws.String(\"docker.io/library/init-container\"),\n\t\t\tRegistryId:         aws.String(\"123456789\"),\n\t\t\tTags: []*ecr.Tag{\n\t\t\t\t{\n\t\t\t\t\tKey:   aws.String(\"CreatedBy\"),\n\t\t\t\t\tValue: aws.String(\"k8s-image-swapper\"),\n\t\t\t\t},\n\t\t\t},\n\t\t}).Return(mock.Anything)\n\n\tregistryClient, _ := registry.NewMockECRClient(ecrClient, \"ap-southeast-2\", \"123456789.dkr.ecr.ap-southeast-2.amazonaws.com\", \"123456789\", \"arn:aws:iam::123456789:role/fakerole\")\n\n\t// image swapper with an instant timeout for testing purpose\n\tmutator := NewImageSwapperWithOpts(\n\t\tregistryClient,\n\t\tImageCopyDeadline(0*time.Second),\n\t)\n\n\timageSwapper, _ := mutator.(*ImageSwapper)\n\n\tsrcRef, _ := alltransports.ParseImageName(\"docker://library/init-container:latest\")\n\ttargetRef, _ := alltransports.ParseImageName(\"docker://123456789.dkr.ecr.ap-southeast-2.amazonaws.com/docker.io/library/init-container:latest\")\n\timageCopier := &ImageCopier{\n\t\timageSwapper:    imageSwapper,\n\t\tcontext:         context.Background(),\n\t\tsourceImageRef:  srcRef,\n\t\ttargetImageRef:  targetRef,\n\t\timagePullPolicy: corev1.PullAlways,\n\t\tsourcePod: &corev1.Pod{\n\t\t\tSpec: corev1.PodSpec{\n\t\t\t\tServiceAccountName: \"service-account\",\n\t\t\t\tImagePullSecrets:   []corev1.LocalObjectReference{},\n\t\t\t},\n\t\t},\n\t}\n\timageCopier = imageCopier.withDeadline()\n\n\t// test that copy steps generate timeout errors\n\tvar timeoutError error\n\n\ttimeoutError = imageCopier.run(imageCopier.taskCheckImage)\n\tassert.Equal(t, context.DeadlineExceeded, timeoutError)\n\n\ttimeoutError = imageCopier.run(imageCopier.taskCreateRepository)\n\tassert.Equal(t, context.DeadlineExceeded, timeoutError)\n\n\ttimeoutError = imageCopier.run(imageCopier.taskCopyImage)\n\tassert.Equal(t, context.DeadlineExceeded, timeoutError)\n\n\ttimeoutError = imageCopier.taskCheckImage()\n\tassert.Equal(t, context.DeadlineExceeded, timeoutError)\n\n\ttimeoutError = imageCopier.taskCreateRepository()\n\tassert.Equal(t, context.DeadlineExceeded, timeoutError)\n\n\ttimeoutError = imageCopier.taskCopyImage()\n\tassert.Equal(t, context.DeadlineExceeded, timeoutError)\n}\n"
  },
  {
    "path": "pkg/webhook/image_swapper.go",
    "content": "package webhook\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/alitto/pond\"\n\t\"github.com/containers/image/v5/docker/reference\"\n\t\"github.com/containers/image/v5/transports/alltransports\"\n\tctypes \"github.com/containers/image/v5/types\"\n\t\"github.com/estahn/k8s-image-swapper/pkg/config\"\n\t\"github.com/estahn/k8s-image-swapper/pkg/registry\"\n\t\"github.com/estahn/k8s-image-swapper/pkg/secrets\"\n\ttypes \"github.com/estahn/k8s-image-swapper/pkg/types\"\n\tjmespath \"github.com/jmespath/go-jmespath\"\n\t\"github.com/rs/zerolog/log\"\n\tkwhmodel \"github.com/slok/kubewebhook/v2/pkg/model\"\n\t\"github.com/slok/kubewebhook/v2/pkg/webhook\"\n\tkwhmutating \"github.com/slok/kubewebhook/v2/pkg/webhook/mutating\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// Option represents an option that can be passed when instantiating the image swapper to customize it\ntype Option func(*ImageSwapper)\n\n// ImagePullSecretsProvider allows to pass a provider reading out Kubernetes secrets\nfunc ImagePullSecretsProvider(provider secrets.ImagePullSecretsProvider) Option {\n\treturn func(swapper *ImageSwapper) {\n\t\tswapper.imagePullSecretProvider = provider\n\t}\n}\n\n// Filters allows to pass JMESPathFilter to select the images to be swapped\nfunc Filters(filters []config.JMESPathFilter) Option {\n\treturn func(swapper *ImageSwapper) {\n\t\tswapper.filters = filters\n\t}\n}\n\n// ImageSwapPolicy allows to pass the ImageSwapPolicy option\nfunc ImageSwapPolicy(policy types.ImageSwapPolicy) Option {\n\treturn func(swapper *ImageSwapper) {\n\t\tswapper.imageSwapPolicy = policy\n\t}\n}\n\n// ImageCopyPolicy allows to pass the ImageCopyPolicy option\nfunc ImageCopyPolicy(policy types.ImageCopyPolicy) Option {\n\treturn func(swapper *ImageSwapper) {\n\t\tswapper.imageCopyPolicy = policy\n\t}\n}\n\n// ImageCopyDeadline allows to pass the ImageCopyPolicy option\nfunc ImageCopyDeadline(deadline time.Duration) Option {\n\treturn func(swapper *ImageSwapper) {\n\t\tswapper.imageCopyDeadline = deadline\n\t}\n}\n\n// Copier allows to pass the copier option\nfunc Copier(pool *pond.WorkerPool) Option {\n\treturn func(swapper *ImageSwapper) {\n\t\tswapper.copier = pool\n\t}\n}\n\n// ImageSwapper is a mutator that will download images and change the image name.\ntype ImageSwapper struct {\n\tregistryClient          registry.Client\n\timagePullSecretProvider secrets.ImagePullSecretsProvider\n\n\t// filters defines a list of expressions to remove objects that should not be processed,\n\t// by default all objects will be processed\n\tfilters []config.JMESPathFilter\n\n\t// copier manages the jobs copying the images to the target registry\n\tcopier            *pond.WorkerPool\n\timageCopyDeadline time.Duration\n\n\timageSwapPolicy types.ImageSwapPolicy\n\timageCopyPolicy types.ImageCopyPolicy\n}\n\n// NewImageSwapper returns a new ImageSwapper initialized.\nfunc NewImageSwapper(registryClient registry.Client, imagePullSecretProvider secrets.ImagePullSecretsProvider, filters []config.JMESPathFilter, imageSwapPolicy types.ImageSwapPolicy, imageCopyPolicy types.ImageCopyPolicy, imageCopyDeadline time.Duration) kwhmutating.Mutator {\n\treturn &ImageSwapper{\n\t\tregistryClient:          registryClient,\n\t\timagePullSecretProvider: imagePullSecretProvider,\n\t\tfilters:                 filters,\n\t\tcopier:                  pond.New(100, 1000),\n\t\timageSwapPolicy:         imageSwapPolicy,\n\t\timageCopyPolicy:         imageCopyPolicy,\n\t\timageCopyDeadline:       imageCopyDeadline,\n\t}\n}\n\n// NewImageSwapperWithOpts returns a configured ImageSwapper instance\nfunc NewImageSwapperWithOpts(registryClient registry.Client, opts ...Option) kwhmutating.Mutator {\n\tswapper := &ImageSwapper{\n\t\tregistryClient:          registryClient,\n\t\timagePullSecretProvider: secrets.NewDummyImagePullSecretsProvider(),\n\t\tfilters:                 []config.JMESPathFilter{},\n\t\timageSwapPolicy:         types.ImageSwapPolicyExists,\n\t\timageCopyPolicy:         types.ImageCopyPolicyDelayed,\n\t}\n\n\tfor _, opt := range opts {\n\t\topt(swapper)\n\t}\n\n\t// Initialise worker pool if not configured\n\tif swapper.copier == nil {\n\t\tswapper.copier = pond.New(100, 1000)\n\t}\n\n\treturn swapper\n}\n\nfunc NewImageSwapperWebhookWithOpts(registryClient registry.Client, opts ...Option) (webhook.Webhook, error) {\n\timageSwapper := NewImageSwapperWithOpts(registryClient, opts...)\n\tmt := kwhmutating.MutatorFunc(imageSwapper.Mutate)\n\tmcfg := kwhmutating.WebhookConfig{\n\t\tID:      \"k8s-image-swapper\",\n\t\tObj:     &corev1.Pod{},\n\t\tMutator: mt,\n\t}\n\n\treturn kwhmutating.NewWebhook(mcfg)\n}\n\nfunc NewImageSwapperWebhook(registryClient registry.Client, imagePullSecretProvider secrets.ImagePullSecretsProvider, filters []config.JMESPathFilter, imageSwapPolicy types.ImageSwapPolicy, imageCopyPolicy types.ImageCopyPolicy, imageCopyDeadline time.Duration) (webhook.Webhook, error) {\n\timageSwapper := NewImageSwapper(registryClient, imagePullSecretProvider, filters, imageSwapPolicy, imageCopyPolicy, imageCopyDeadline)\n\tmt := kwhmutating.MutatorFunc(imageSwapper.Mutate)\n\tmcfg := kwhmutating.WebhookConfig{\n\t\tID:      \"k8s-image-swapper\",\n\t\tObj:     &corev1.Pod{},\n\t\tMutator: mt,\n\t}\n\n\treturn kwhmutating.NewWebhook(mcfg)\n}\n\n// imageNamesWithDigestOrTag strips the tag from ambiguous image references that have a digest as well (e.g. `image:tag@sha256:123...`).\n// Such image references are supported by docker but, due to their ambiguity,\n// explicitly not by containers/image.\nfunc imageNamesWithDigestOrTag(imageName string) (string, error) {\n\tref, err := reference.ParseNormalizedNamed(imageName)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\t_, isTagged := ref.(reference.NamedTagged)\n\tcanonical, isDigested := ref.(reference.Canonical)\n\tif isTagged && isDigested {\n\t\tcanonical, err = reference.WithDigest(reference.TrimNamed(ref), canonical.Digest())\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\timageName = canonical.String()\n\t}\n\treturn imageName, nil\n}\n\n// Mutate replaces the image ref. Satisfies mutating.Mutator interface.\nfunc (p *ImageSwapper) Mutate(ctx context.Context, ar *kwhmodel.AdmissionReview, obj metav1.Object) (*kwhmutating.MutatorResult, error) {\n\tpod, ok := obj.(*corev1.Pod)\n\tif !ok {\n\t\treturn &kwhmutating.MutatorResult{}, nil\n\t}\n\n\tlogger := log.With().\n\t\tStr(\"uid\", string(ar.ID)).\n\t\tStr(\"kind\", ar.RequestGVK.String()).\n\t\tStr(\"namespace\", ar.Namespace).\n\t\tStr(\"name\", pod.Name).\n\t\tLogger()\n\n\tlctx := logger.WithContext(context.Background())\n\n\tcontainerSets := []*[]corev1.Container{&pod.Spec.Containers, &pod.Spec.InitContainers}\n\tfor _, containerSet := range containerSets {\n\t\tcontainers := *containerSet\n\t\tfor i, container := range containers {\n\t\t\tnormalizedName, err := imageNamesWithDigestOrTag(container.Image)\n\t\t\tif err != nil {\n\t\t\t\tlog.Ctx(lctx).Warn().Msgf(\"unable to normalize source name %s: %v\", container.Image, err)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tsrcRef, err := alltransports.ParseImageName(\"docker://\" + normalizedName)\n\t\t\tif err != nil {\n\t\t\t\tlog.Ctx(lctx).Warn().Msgf(\"invalid source name %s: %v\", normalizedName, err)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\t// skip if the source originates from the target registry\n\t\t\tif p.registryClient.IsOrigin(srcRef) {\n\t\t\t\tlog.Ctx(lctx).Debug().Str(\"registry\", srcRef.DockerReference().String()).Msg(\"skip due to source and target being the same registry\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tfilterCtx := NewFilterContext(*ar, pod, container)\n\t\t\tif filterMatch(filterCtx, p.filters) {\n\t\t\t\tlog.Ctx(lctx).Debug().Msg(\"skip due to filter condition\")\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\ttargetRef := p.targetRef(srcRef)\n\t\t\ttargetImage := targetRef.DockerReference().String()\n\n\t\t\timageCopierLogger := logger.With().\n\t\t\t\tStr(\"source-image\", srcRef.DockerReference().String()).\n\t\t\t\tStr(\"target-image\", targetImage).\n\t\t\t\tLogger()\n\n\t\t\timageCopierContext := imageCopierLogger.WithContext(lctx)\n\t\t\t// create an object responsible for the image copy\n\t\t\timageCopier := ImageCopier{\n\t\t\t\tsourcePod:       pod,\n\t\t\t\tsourceImageRef:  srcRef,\n\t\t\t\ttargetImageRef:  targetRef,\n\t\t\t\timagePullPolicy: container.ImagePullPolicy,\n\t\t\t\timageSwapper:    p,\n\t\t\t\tcontext:         imageCopierContext,\n\t\t\t}\n\n\t\t\t// imageCopyPolicy\n\t\t\tswitch p.imageCopyPolicy {\n\t\t\tcase types.ImageCopyPolicyDelayed:\n\t\t\t\tp.copier.Submit(imageCopier.start)\n\t\t\tcase types.ImageCopyPolicyImmediate:\n\t\t\t\tp.copier.SubmitAndWait(imageCopier.withDeadline().start)\n\t\t\tcase types.ImageCopyPolicyForce:\n\t\t\t\timageCopier.withDeadline().start()\n\t\t\tcase types.ImageCopyPolicyNone:\n\t\t\t\t// do not copy image\n\t\t\tdefault:\n\t\t\t\tpanic(\"unknown imageCopyPolicy\")\n\t\t\t}\n\n\t\t\t// imageSwapPolicy\n\t\t\tswitch p.imageSwapPolicy {\n\t\t\tcase types.ImageSwapPolicyAlways:\n\t\t\t\tlog.Ctx(lctx).Debug().Str(\"image\", targetImage).Msg(\"set new container image\")\n\t\t\t\tcontainers[i].Image = targetImage\n\t\t\tcase types.ImageSwapPolicyExists:\n\t\t\t\tif p.registryClient.ImageExists(lctx, targetRef) {\n\t\t\t\t\tlog.Ctx(lctx).Debug().Str(\"image\", targetImage).Msg(\"set new container image\")\n\t\t\t\t\tcontainers[i].Image = targetImage\n\t\t\t\t} else {\n\t\t\t\t\tlog.Ctx(lctx).Debug().Str(\"image\", targetImage).Msg(\"container image not found in target registry, not swapping\")\n\t\t\t\t}\n\t\t\tdefault:\n\t\t\t\tpanic(\"unknown imageSwapPolicy\")\n\t\t\t}\n\t\t}\n\t}\n\n\treturn &kwhmutating.MutatorResult{MutatedObject: pod}, nil\n}\n\n// filterMatch returns true if one of the filters matches the context\nfunc filterMatch(ctx FilterContext, filters []config.JMESPathFilter) bool {\n\t// Simplify FilterContext to be easier searchable by marshaling it to JSON and back to an interface\n\tvar filterContext interface{}\n\tjsonBlob, err := json.Marshal(ctx)\n\tif err != nil {\n\t\tlog.Err(err).Msg(\"could not marshal filter context\")\n\t\treturn false\n\t}\n\n\terr = json.Unmarshal(jsonBlob, &filterContext)\n\tif err != nil {\n\t\tlog.Err(err).Msg(\"could not unmarshal json blob\")\n\t\treturn false\n\t}\n\n\tlog.Debug().Interface(\"object\", filterContext).Msg(\"generated filter context\")\n\n\tfor idx, filter := range filters {\n\t\tresults, err := jmespath.Search(filter.JMESPath, filterContext)\n\t\tlog.Debug().Str(\"filter\", filter.JMESPath).Interface(\"results\", results).Msg(\"jmespath search results\")\n\n\t\tif err != nil {\n\t\t\tlog.Err(err).Str(\"filter\", filter.JMESPath).Msgf(\"Filter (idx %v) could not be evaluated.\", idx)\n\t\t\treturn false\n\t\t}\n\n\t\tswitch results.(type) {\n\t\tcase bool:\n\t\t\tif results == true {\n\t\t\t\treturn true\n\t\t\t}\n\t\tdefault:\n\t\t\tlog.Warn().Str(\"filter\", filter.JMESPath).Msg(\"filter does not return a bool value\")\n\t\t}\n\t}\n\n\treturn false\n}\n\n// targetName returns the reference in the target repository\nfunc (p *ImageSwapper) targetRef(srcRef ctypes.ImageReference) ctypes.ImageReference {\n\ttargetImage := fmt.Sprintf(\"%s/%s\", p.registryClient.Endpoint(), srcRef.DockerReference().String())\n\n\tref, err := alltransports.ParseImageName(\"docker://\" + targetImage)\n\tif err != nil {\n\t\tlog.Warn().Msgf(\"invalid target name %s: %v\", targetImage, err)\n\t}\n\n\treturn ref\n}\n\n// FilterContext is being used by JMESPath to search and match\ntype FilterContext struct {\n\t// Obj contains the object submitted to the webhook (currently only pods)\n\tObj metav1.Object `json:\"obj,omitempty\"`\n\n\t// Container contains the currently processed container\n\tContainer corev1.Container `json:\"container,omitempty\"`\n}\n\nfunc NewFilterContext(request kwhmodel.AdmissionReview, obj metav1.Object, container corev1.Container) FilterContext {\n\tif obj.GetNamespace() == \"\" {\n\t\tobj.SetNamespace(request.Namespace)\n\t}\n\n\treturn FilterContext{Obj: obj, Container: container}\n}\n"
  },
  {
    "path": "pkg/webhook/image_swapper_test.go",
    "content": "package webhook\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/alitto/pond\"\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/aws/request\"\n\t\"github.com/aws/aws-sdk-go/service/ecr\"\n\t\"github.com/aws/aws-sdk-go/service/ecr/ecriface\"\n\t\"github.com/estahn/k8s-image-swapper/pkg/config\"\n\t\"github.com/estahn/k8s-image-swapper/pkg/registry\"\n\t\"github.com/estahn/k8s-image-swapper/pkg/secrets\"\n\t\"github.com/estahn/k8s-image-swapper/pkg/types\"\n\t\"github.com/slok/kubewebhook/v2/pkg/model\"\n\t\"github.com/stretchr/testify/assert\"\n\t\"github.com/stretchr/testify/mock\"\n\tadmissionv1 \"k8s.io/api/admission/v1\"\n\tcorev1 \"k8s.io/api/core/v1\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n\t\"k8s.io/client-go/kubernetes/fake\"\n)\n\n//func TestImageSwapperMutator(t *testing.T) {\n//\ttests := []struct {\n//\t\tname   string\n//\t\tpod    *corev1.Pod\n//\t\tlabels map[string]string\n//\t\texpPod *corev1.Pod\n//\t\texpErr bool\n//\t}{\n//\t\t{\n//\t\t\tname: \"Prefix docker hub images with host docker.io.\",\n//\t\t\tpod: &corev1.Pod{\n//\t\t\t\tSpec: corev1.PodSpec{\n//\t\t\t\t\tContainers: []corev1.Container{\n//\t\t\t\t\t\t{\n//\t\t\t\t\t\t\tImage: \"nginx:latest\",\n//\t\t\t\t\t\t},\n//\t\t\t\t\t},\n//\t\t\t\t},\n//\t\t\t},\n//\t\t\texpPod: &corev1.Pod{\n//\t\t\t\tSpec: corev1.PodSpec{\n//\t\t\t\t\tContainers: []corev1.Container{\n//\t\t\t\t\t\t{\n//\t\t\t\t\t\t\tImage: \"foobar.com/docker.io/nginx:latest\",\n//\t\t\t\t\t\t},\n//\t\t\t\t\t},\n//\t\t\t\t},\n//\t\t\t},\n//\t\t},\n//\t\t{\n//\t\t\tname: \"Don't mutate if targetRegistry host is target targetRegistry.\",\n//\t\t\tpod: &corev1.Pod{\n//\t\t\t\tSpec: corev1.PodSpec{\n//\t\t\t\t\tContainers: []corev1.Container{\n//\t\t\t\t\t\t{\n//\t\t\t\t\t\t\tImage: \"foobar.com/docker.io/nginx:latest\",\n//\t\t\t\t\t\t},\n//\t\t\t\t\t},\n//\t\t\t\t},\n//\t\t\t},\n//\t\t\texpPod: &corev1.Pod{\n//\t\t\t\tSpec: corev1.PodSpec{\n//\t\t\t\t\tContainers: []corev1.Container{\n//\t\t\t\t\t\t{\n//\t\t\t\t\t\t\tImage: \"foobar.com/docker.io/nginx:latest\",\n//\t\t\t\t\t\t},\n//\t\t\t\t\t},\n//\t\t\t\t},\n//\t\t\t},\n//\t\t},\n//\t}\n//\n//\tfor _, test := range tests {\n//\t\tt.Run(test.name, func(t *testing.T) {\n//\t\t\tassert := assert.New(t)\n//\n//\t\t\tpl := NewImageSwapper(\"foobar.com\")\n//\n//\t\t\tgotPod := test.pod\n//\t\t\t_, err := pl.Mutate(context.TODO(), gotPod)\n//\n//\t\t\tif test.expErr {\n//\t\t\t\tassert.Error(err)\n//\t\t\t} else if assert.NoError(err) {\n//\t\t\t\tassert.Equal(test.expPod, gotPod)\n//\t\t\t}\n//\t\t})\n//\t}\n//\n//}\n//\n//func TestAnnotatePodMutator2(t *testing.T) {\n//\ttests := []struct {\n//\t\tname   string\n//\t\tpod    *corev1.Pod\n//\t\tlabels map[string]string\n//\t\texpPod *corev1.Pod\n//\t\texpErr bool\n//\t}{\n//\t\t{\n//\t\t\tname: \"Mutating a pod without labels should set the labels correctly.\",\n//\t\t\tpod: &corev1.Pod{\n//\t\t\t\tObjectMeta: metav1.ObjectMeta{\n//\t\t\t\t\tName: \"test\",\n//\t\t\t\t},\n//\t\t\t},\n//\t\t\tlabels: map[string]string{\"bruce\": \"wayne\", \"peter\": \"parker\"},\n//\t\t\texpPod: &corev1.Pod{\n//\t\t\t\tObjectMeta: metav1.ObjectMeta{\n//\t\t\t\t\tName:   \"test\",\n//\t\t\t\t\tLabels: map[string]string{\"bruce\": \"wayne\", \"peter\": \"parker\"},\n//\t\t\t\t},\n//\t\t\t},\n//\t\t},\n//\t\t{\n//\t\t\tname: \"Mutating a pod with labels should aggregate and replace the labels with the existing ones.\",\n//\t\t\tpod: &corev1.Pod{\n//\t\t\t\tObjectMeta: metav1.ObjectMeta{\n//\t\t\t\t\tName:   \"test\",\n//\t\t\t\t\tLabels: map[string]string{\"bruce\": \"banner\", \"tony\": \"stark\"},\n//\t\t\t\t},\n//\t\t\t},\n//\t\t\tlabels: map[string]string{\"bruce\": \"wayne\", \"peter\": \"parker\"},\n//\t\t\texpPod: &corev1.Pod{\n//\t\t\t\tObjectMeta: metav1.ObjectMeta{\n//\t\t\t\t\tName:   \"test\",\n//\t\t\t\t\tLabels: map[string]string{\"bruce\": \"wayne\", \"peter\": \"parker\", \"tony\": \"stark\"},\n//\t\t\t\t},\n//\t\t\t},\n//\t\t},\n//\t}\n//\n//\tfor _, test := range tests {\n//\t\tt.Run(test.name, func(t *testing.T) {\n//\t\t\tassert := assert.New(t)\n//\n//\t\t\tpl := mutatortesting.NewPodLabeler(test.labels)\n//\t\t\tgotPod := test.pod\n//\t\t\t_, err := pl.Mutate(context.TODO(), gotPod)\n//\n//\t\t\tif test.expErr {\n//\t\t\t\tassert.Error(err)\n//\t\t\t} else if assert.NoError(err) {\n//\t\t\t\t// Check the expected pod.\n//\t\t\t\tassert.Equal(test.expPod, gotPod)\n//\t\t\t}\n//\t\t})\n//\t}\n//\n//}\n\n//func TestRegistryHost(t *testing.T) {\n//\tassert.Equal(t, \"\", registryDomain(\"nginx:latest\"))\n//\tassert.Equal(t, \"docker.io\", registryDomain(\"docker.io/nginx:latest\"))\n//}\n\nfunc TestFilterMatch(t *testing.T) {\n\tfilterContext := FilterContext{\n\t\tObj: &corev1.Pod{\n\t\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\t\tNamespace: \"kube-system\",\n\t\t\t},\n\t\t\tSpec: corev1.PodSpec{\n\t\t\t\tContainers: []corev1.Container{\n\t\t\t\t\t{\n\t\t\t\t\t\tName:  \"nginx\",\n\t\t\t\t\t\tImage: \"nginx:latest\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tContainer: corev1.Container{\n\t\t\tName:  \"nginx\",\n\t\t\tImage: \"nginx:latest\",\n\t\t},\n\t}\n\n\tassert.True(t, filterMatch(filterContext, []config.JMESPathFilter{{JMESPath: \"obj.metadata.namespace == 'kube-system'\"}}))\n\tassert.False(t, filterMatch(filterContext, []config.JMESPathFilter{{JMESPath: \"obj.metadata.namespace != 'kube-system'\"}}))\n\tassert.False(t, filterMatch(filterContext, []config.JMESPathFilter{{JMESPath: \"obj\"}}))\n\tassert.True(t, filterMatch(filterContext, []config.JMESPathFilter{{JMESPath: \"container.name == 'nginx'\"}}))\n\t// false syntax test\n\tassert.False(t, filterMatch(filterContext, []config.JMESPathFilter{{JMESPath: \".\"}}))\n\t// non-boolean value\n\tassert.False(t, filterMatch(filterContext, []config.JMESPathFilter{{JMESPath: \"obj\"}}))\n\tassert.False(t, filterMatch(filterContext, []config.JMESPathFilter{{JMESPath: \"contains(container.image, '.dkr.ecr.') && contains(container.image, '.amazonaws.com')\"}}))\n}\n\ntype mockECRClient struct {\n\tmock.Mock\n\tecriface.ECRAPI\n}\n\nfunc (m *mockECRClient) CreateRepositoryWithContext(ctx context.Context, createRepositoryInput *ecr.CreateRepositoryInput, opts ...request.Option) (*ecr.CreateRepositoryOutput, error) {\n\tif ctx.Err() != nil {\n\t\treturn nil, ctx.Err()\n\t}\n\n\tm.Called(ctx, createRepositoryInput)\n\treturn &ecr.CreateRepositoryOutput{}, nil\n}\n\nfunc TestHelperProcess(t *testing.T) {\n\tif os.Getenv(\"GO_WANT_HELPER_PROCESS\") != \"1\" {\n\t\treturn\n\t}\n\tos.Exit(0)\n}\n\nfunc readAdmissionReviewFromFile(filename string) (*admissionv1.AdmissionReview, error) {\n\tdata, err := os.ReadFile(\"../../test/requests/\" + filename)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tar := &admissionv1.AdmissionReview{}\n\tif err := json.Unmarshal(data, ar); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn ar, nil\n}\n\nfunc TestImageSwapper_Mutate(t *testing.T) {\n\texpectedRepositories := []string{\n\t\t\"docker.io/library/init-container\",\n\t\t\"docker.io/library/nginx\",\n\t\t\"k8s.gcr.io/ingress-nginx/controller\",\n\t\t\"us-central1-docker.pkg.dev/gcp-project-123/main/k8s.gcr.io/ingress-nginx/controller\",\n\t}\n\n\tecrClient := new(mockECRClient)\n\n\tfor _, expectedRepository := range expectedRepositories {\n\t\tecrClient.On(\n\t\t\t\"CreateRepositoryWithContext\",\n\t\t\tmock.AnythingOfType(\"*context.valueCtx\"),\n\t\t\t&ecr.CreateRepositoryInput{\n\t\t\t\tImageScanningConfiguration: &ecr.ImageScanningConfiguration{\n\t\t\t\t\tScanOnPush: aws.Bool(true),\n\t\t\t\t},\n\t\t\t\tEncryptionConfiguration: &ecr.EncryptionConfiguration{\n\t\t\t\t\tEncryptionType: aws.String(\"AES256\"),\n\t\t\t\t},\n\t\t\t\tImageTagMutability: aws.String(\"MUTABLE\"),\n\t\t\t\tRepositoryName:     aws.String(expectedRepository),\n\t\t\t\tRegistryId:         aws.String(\"123456789\"),\n\t\t\t\tTags: []*ecr.Tag{\n\t\t\t\t\t{\n\t\t\t\t\t\tKey:   aws.String(\"CreatedBy\"),\n\t\t\t\t\t\tValue: aws.String(\"k8s-image-swapper\"),\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tKey:   aws.String(\"AnotherTag\"),\n\t\t\t\t\t\tValue: aws.String(\"another-tag\"),\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}).Return(mock.Anything)\n\t}\n\n\tregistryClient, _ := registry.NewMockECRClient(ecrClient, \"ap-southeast-2\", \"123456789.dkr.ecr.ap-southeast-2.amazonaws.com\", \"123456789\", \"arn:aws:iam::123456789:role/fakerole\")\n\n\tadmissionReview, _ := readAdmissionReviewFromFile(\"admissionreview-simple.json\")\n\tadmissionReviewModel := model.NewAdmissionReviewV1(admissionReview)\n\n\tcopier := pond.New(1, 1)\n\t// TODO: test types.ImageSwapPolicyExists\n\twh, err := NewImageSwapperWebhookWithOpts(\n\t\tregistryClient,\n\t\tCopier(copier),\n\t\tImageSwapPolicy(types.ImageSwapPolicyAlways),\n\t)\n\n\tassert.NoError(t, err, \"NewImageSwapperWebhookWithOpts executed without errors\")\n\n\tresp, err := wh.Review(context.Background(), admissionReviewModel)\n\n\t// TODO: think about moving \"expected\" into a file, e.g. admissionreview-simple-response-ecr.json\n\t// container with name \"skip-test-gar\" should be skipped, hence there is no \"replace\" operation for it\n\texpected := `[\n\t\t{\"op\":\"replace\",\"path\":\"/spec/initContainers/0/image\",\"value\":\"123456789.dkr.ecr.ap-southeast-2.amazonaws.com/docker.io/library/init-container:latest\"},\n\t\t{\"op\":\"replace\",\"path\":\"/spec/containers/0/image\",\"value\":\"123456789.dkr.ecr.ap-southeast-2.amazonaws.com/docker.io/library/nginx:latest\"},\n\t\t{\"op\":\"replace\",\"path\":\"/spec/containers/1/image\",\"value\":\"123456789.dkr.ecr.ap-southeast-2.amazonaws.com/k8s.gcr.io/ingress-nginx/controller@sha256:9bba603b99bf25f6d117cf1235b6598c16033ad027b143c90fa5b3cc583c5713\"},\n\t\t{\"op\":\"replace\",\"path\":\"/spec/containers/3/image\",\"value\":\"123456789.dkr.ecr.ap-southeast-2.amazonaws.com/us-central1-docker.pkg.dev/gcp-project-123/main/k8s.gcr.io/ingress-nginx/controller@sha256:9bba603b99bf25f6d117cf1235b6598c16033ad027b143c90fa5b3cc583c5713\"}\n\t]`\n\n\tassert.JSONEq(t, expected, string(resp.(*model.MutatingAdmissionResponse).JSONPatchPatch))\n\tassert.Nil(t, resp.(*model.MutatingAdmissionResponse).Warnings)\n\tassert.NoError(t, err, \"Webhook executed without errors\")\n\n\t// Ensure the worker pool is empty before asserting ecrClient\n\tcopier.StopAndWait()\n\n\tecrClient.AssertExpectations(t)\n}\n\n// TestImageSwapper_MutateWithImagePullSecrets tests mutating with imagePullSecret support\nfunc TestImageSwapper_MutateWithImagePullSecrets(t *testing.T) {\n\tecrClient := new(mockECRClient)\n\tecrClient.On(\n\t\t\"CreateRepositoryWithContext\",\n\t\tmock.AnythingOfType(\"*context.valueCtx\"),\n\t\t&ecr.CreateRepositoryInput{\n\t\t\tImageScanningConfiguration: &ecr.ImageScanningConfiguration{\n\t\t\t\tScanOnPush: aws.Bool(true),\n\t\t\t},\n\t\t\tEncryptionConfiguration: &ecr.EncryptionConfiguration{\n\t\t\t\tEncryptionType: aws.String(\"AES256\"),\n\t\t\t},\n\t\t\tImageTagMutability: aws.String(\"MUTABLE\"),\n\t\t\tRegistryId:         aws.String(\"123456789\"),\n\t\t\tRepositoryName:     aws.String(\"docker.io/library/nginx\"),\n\t\t\tTags: []*ecr.Tag{\n\t\t\t\t{\n\t\t\t\t\tKey:   aws.String(\"CreatedBy\"),\n\t\t\t\t\tValue: aws.String(\"k8s-image-swapper\"),\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tKey:   aws.String(\"AnotherTag\"),\n\t\t\t\t\tValue: aws.String(\"another-tag\"),\n\t\t\t\t},\n\t\t\t},\n\t\t}).Return(mock.Anything)\n\n\tregistryClient, _ := registry.NewMockECRClient(ecrClient, \"ap-southeast-2\", \"123456789.dkr.ecr.ap-southeast-2.amazonaws.com\", \"123456789\", \"arn:aws:iam::123456789:role/fakerole\")\n\n\tadmissionReview, _ := readAdmissionReviewFromFile(\"admissionreview-imagepullsecrets.json\")\n\tadmissionReviewModel := model.NewAdmissionReviewV1(admissionReview)\n\n\tclientSet := fake.NewSimpleClientset()\n\n\tsvcAccount := &corev1.ServiceAccount{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: \"my-service-account\",\n\t\t},\n\t\tImagePullSecrets: []corev1.LocalObjectReference{\n\t\t\t{Name: \"my-sa-secret\"},\n\t\t},\n\t}\n\tsvcAccountSecretDockerConfigJson := []byte(`{\"auths\":{\"my-sa-secret.registry.example.com\":{\"username\":\"my-sa-secret\",\"password\":\"xxxxxxxxxxx\",\"email\":\"jdoe@example.com\",\"auth\":\"c3R...zE2\"}}}`)\n\tsvcAccountSecret := &corev1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: \"my-sa-secret\",\n\t\t},\n\t\tType: corev1.SecretTypeDockerConfigJson,\n\t\tData: map[string][]byte{\n\t\t\tcorev1.DockerConfigJsonKey: svcAccountSecretDockerConfigJson,\n\t\t},\n\t}\n\tpodSecretDockerConfigJson := []byte(`{\"auths\":{\"my-pod-secret.registry.example.com\":{\"username\":\"my-sa-secret\",\"password\":\"xxxxxxxxxxx\",\"email\":\"jdoe@example.com\",\"auth\":\"c3R...zE2\"}}}`)\n\tpodSecret := &corev1.Secret{\n\t\tObjectMeta: metav1.ObjectMeta{\n\t\t\tName: \"my-pod-secret\",\n\t\t},\n\t\tType: corev1.SecretTypeDockerConfigJson,\n\t\tData: map[string][]byte{\n\t\t\tcorev1.DockerConfigJsonKey: podSecretDockerConfigJson,\n\t\t},\n\t}\n\n\t_, _ = clientSet.CoreV1().ServiceAccounts(\"test-ns\").Create(context.Background(), svcAccount, metav1.CreateOptions{})\n\t_, _ = clientSet.CoreV1().Secrets(\"test-ns\").Create(context.Background(), svcAccountSecret, metav1.CreateOptions{})\n\t_, _ = clientSet.CoreV1().Secrets(\"test-ns\").Create(context.Background(), podSecret, metav1.CreateOptions{})\n\n\tprovider := secrets.NewKubernetesImagePullSecretsProvider(clientSet)\n\n\tcopier := pond.New(1, 1)\n\t// TODO: test types.ImageSwapPolicyExists\n\twh, err := NewImageSwapperWebhookWithOpts(\n\t\tregistryClient,\n\t\tImagePullSecretsProvider(provider),\n\t\tCopier(copier),\n\t\tImageSwapPolicy(types.ImageSwapPolicyAlways),\n\t)\n\n\tassert.NoError(t, err, \"NewImageSwapperWebhookWithOpts executed without errors\")\n\n\tresp, err := wh.Review(context.Background(), admissionReviewModel)\n\n\tassert.JSONEq(t, \"[{\\\"op\\\":\\\"replace\\\",\\\"path\\\":\\\"/spec/containers/0/image\\\",\\\"value\\\":\\\"123456789.dkr.ecr.ap-southeast-2.amazonaws.com/docker.io/library/nginx:latest\\\"}]\", string(resp.(*model.MutatingAdmissionResponse).JSONPatchPatch))\n\tassert.Nil(t, resp.(*model.MutatingAdmissionResponse).Warnings)\n\tassert.NoError(t, err, \"Webhook executed without errors\")\n\n\t// Ensure the worker pool is empty before asserting ecrClient\n\tcopier.StopAndWait()\n\n\tecrClient.AssertExpectations(t)\n}\n\nfunc TestImageSwapper_GAR_Mutate(t *testing.T) {\n\tregistryClient, _ := registry.NewMockGARClient(nil, \"us-central1-docker.pkg.dev/gcp-project-123/main\")\n\n\tadmissionReview, _ := readAdmissionReviewFromFile(\"admissionreview-simple.json\")\n\tadmissionReviewModel := model.NewAdmissionReviewV1(admissionReview)\n\n\tcopier := pond.New(1, 1)\n\t// TODO: test types.ImageSwapPolicyExists\n\twh, err := NewImageSwapperWebhookWithOpts(\n\t\tregistryClient,\n\t\tCopier(copier),\n\t\tImageSwapPolicy(types.ImageSwapPolicyAlways),\n\t)\n\n\tassert.NoError(t, err, \"NewImageSwapperWebhookWithOpts executed without errors\")\n\n\tresp, err := wh.Review(context.TODO(), admissionReviewModel)\n\n\t// container with name \"skip-test-gar\" should be skipped, hence there is no \"replace\" operation for it\n\texpected := `[\n\t\t{\"op\":\"replace\",\"path\":\"/spec/initContainers/0/image\",\"value\":\"us-central1-docker.pkg.dev/gcp-project-123/main/docker.io/library/init-container:latest\"},\n\t\t{\"op\":\"replace\",\"path\":\"/spec/containers/0/image\",\"value\":\"us-central1-docker.pkg.dev/gcp-project-123/main/docker.io/library/nginx:latest\"},\n\t\t{\"op\":\"replace\",\"path\":\"/spec/containers/1/image\",\"value\":\"us-central1-docker.pkg.dev/gcp-project-123/main/k8s.gcr.io/ingress-nginx/controller@sha256:9bba603b99bf25f6d117cf1235b6598c16033ad027b143c90fa5b3cc583c5713\"},\n\t\t{\"op\":\"replace\",\"path\":\"/spec/containers/2/image\",\"value\":\"us-central1-docker.pkg.dev/gcp-project-123/main/123456789.dkr.ecr.ap-southeast-2.amazonaws.com/k8s.gcr.io/ingress-nginx/controller@sha256:9bba603b99bf25f6d117cf1235b6598c16033ad027b143c90fa5b3cc583c5713\"}\n\t]`\n\n\tassert.JSONEq(t, expected, string(resp.(*model.MutatingAdmissionResponse).JSONPatchPatch))\n\tassert.Nil(t, resp.(*model.MutatingAdmissionResponse).Warnings)\n\tassert.NoError(t, err, \"Webhook executed without errors\")\n}\n"
  },
  {
    "path": "test/curl.sh",
    "content": "#!/bin/bash\n\ndata='{\"kind\":\"AdmissionReview\",\"apiVersion\":\"admission.k8s.io/v1\",\"request\":{\"uid\":\"c78e0c58-7389-4838-b4f5-28d6005c1cc3\",\"kind\":{\"group\":\"\",\"version\":\"v1\",\"kind\":\"Pod\"},\"resource\":{\"group\":\"\",\"version\":\"v1\",\"resource\":\"pods\"},\"requestKind\":{\"group\":\"\",\"version\":\"v1\",\"kind\":\"Pod\"},\"requestResource\":{\"group\":\"\",\"version\":\"v1\",\"resource\":\"pods\"},\"name\":\"nginx28\",\"namespace\":\"default\",\"operation\":\"CREATE\",\"userInfo\":{\"username\":\"kubernetes-admin\",\"groups\":[\"system:masters\",\"system:authenticated\"]},\"object\":{\"kind\":\"Pod\",\"apiVersion\":\"v1\",\"metadata\":{\"name\":\"nginx28\",\"creationTimestamp\":null,\"labels\":{\"run\":\"nginx28\"}},\"spec\":{\"volumes\":[{\"name\":\"default-token-fjtvr\",\"secret\":{\"secretName\":\"default-token-fjtvr\"}}],\"containers\":[{\"name\":\"nginx28\",\"image\":\"nginx\",\"resources\":{},\"volumeMounts\":[{\"name\":\"default-token-fjtvr\",\"readOnly\":true,\"mountPath\":\"/var/run/secrets/kubernetes.io/serviceaccount\"}],\"terminationMessagePath\":\"/dev/termination-log\",\"terminationMessagePolicy\":\"File\",\"imagePullPolicy\":\"Always\"}],\"restartPolicy\":\"Never\",\"terminationGracePeriodSeconds\":30,\"dnsPolicy\":\"ClusterFirst\",\"serviceAccountName\":\"default\",\"serviceAccount\":\"default\",\"securityContext\":{},\"schedulerName\":\"default-scheduler\",\"tolerations\":[{\"key\":\"node.kubernetes.io/not-ready\",\"operator\":\"Exists\",\"effect\":\"NoExecute\",\"tolerationSeconds\":300},{\"key\":\"node.kubernetes.io/unreachable\",\"operator\":\"Exists\",\"effect\":\"NoExecute\",\"tolerationSeconds\":300}],\"priority\":0,\"enableServiceLinks\":true},\"status\":{}},\"oldObject\":null,\"dryRun\":false,\"options\":{\"kind\":\"CreateOptions\",\"apiVersion\":\"meta.k8s.io/v1\",\"fieldManager\":\"kubectl-run\"}}}'\n\ncurl --data \"$data\" http://127.0.0.1:8080/webhook\n"
  },
  {
    "path": "test/e2e_test.go",
    "content": "//go:build integration\n// +build integration\n\npackage test\n\nimport (\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\tawssdk \"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/service/ecr\"\n\t\"github.com/gruntwork-io/terratest/modules/aws\"\n\t\"github.com/gruntwork-io/terratest/modules/helm\"\n\t\"github.com/gruntwork-io/terratest/modules/k8s\"\n\t\"github.com/gruntwork-io/terratest/modules/logger\"\n\t\"github.com/gruntwork-io/terratest/modules/random\"\n\t\"github.com/gruntwork-io/terratest/modules/shell\"\n\ttest_structure \"github.com/gruntwork-io/terratest/modules/test-structure\"\n\tterratesttesting \"github.com/gruntwork-io/terratest/modules/testing\"\n\t\"github.com/stretchr/testify/require\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\n// IsKindCluster returns true if the underlying kubernetes cluster is kind. This is determined by getting the\n// associated nodes and checking if a node is named \"kind-control-plane\".\n//func IsKindCluster(t terratestTesting.TestingT, options *k8s.KubectlOptions) (bool, error) {\n//\tnodes, err := k8s.GetNodesE(t, options)\n//\tif err != nil {\n//\t\treturn false, err\n//\t}\n//\n//\t// ASSUMPTION: All minikube setups will have nodes with labels that are namespaced with minikube.k8s.io\n//\tfor _, node := range nodes {\n//\t\tif !nodeHasMinikubeLabel(node) {\n//\t\t\treturn false, nil\n//\t\t}\n//\t}\n//\n//\t// At this point we know that all the nodes in the cluster has the minikube label, so we return true.\n//\treturn true, nil\n//}\n\n// nodeHasMinikubeLabel returns true if any of the labels on the node is namespaced with minikube.k8s.io\n//func nodeHasMinikubeLabel(node corev1.Node) bool {\n//\tlabels := node.GetLabels()\n//\tfor key, _ := range labels {\n//\t\tif strings.HasPrefix(key, \"minikube.k8s.io\") {\n//\t\t\treturn true\n//\t\t}\n//\t}\n//\treturn false\n//}\n\n// This file contains examples of how to use terratest to test helm charts by deploying the chart and verifying the\n// deployment by hitting the service endpoint.\nfunc TestHelmDeployment(t *testing.T) {\n\tworkingDir, _ := filepath.Abs(\"..\")\n\n\tawsAccountID := os.Getenv(\"AWS_ACCOUNT_ID\")\n\tawsRegion := os.Getenv(\"AWS_DEFAULT_REGION\")\n\tawsAccessKeyID := os.Getenv(\"AWS_ACCESS_KEY_ID\")\n\tawsSecretAccessKey := os.Getenv(\"AWS_SECRET_ACCESS_KEY\")\n\tecrRegistry := awsAccountID + \".dkr.ecr.\" + awsRegion + \".amazonaws.com\"\n\tecrRepository := \"docker.io/library/nginx\"\n\n\tlogger.Default = logger.New(newSensitiveLogger(\n\t\tlogger.Default,\n\t\t[]*regexp.Regexp{\n\t\t\tregexp.MustCompile(awsAccountID),\n\t\t\tregexp.MustCompile(awsAccessKeyID),\n\t\t\tregexp.MustCompile(awsSecretAccessKey),\n\t\t\tregexp.MustCompile(`(--docker-password=)\\S+`),\n\t\t},\n\t))\n\n\t// To ensure we can reuse the resource config on the same cluster to test different scenarios, we setup a unique\n\t// namespace for the resources for this test.\n\t// Note that namespaces must be lowercase.\n\tnamespaceName := fmt.Sprintf(\"k8s-image-swapper-%s\", strings.ToLower(random.UniqueId()))\n\treleaseName := fmt.Sprintf(\"k8s-image-swapper-%s\",\n\t\tstrings.ToLower(random.UniqueId()))\n\n\t// Setup the kubectl config and context. Here we choose to use the defaults, which is:\n\t// - HOME/.kube/config for the kubectl config file\n\t// - Current context of the kubectl config file\n\tkubectlOptions := k8s.NewKubectlOptions(\"\", \"\", namespaceName)\n\n\t// Init ECR client\n\tecrClient := aws.NewECRClient(t, awsRegion)\n\n\tdefer test_structure.RunTestStage(t, \"cleanup_aws\", func() {\n\t\t_, err := ecrClient.DeleteRepository(&ecr.DeleteRepositoryInput{\n\t\t\tRepositoryName: awssdk.String(ecrRepository),\n\t\t\tRegistryId:     awssdk.String(awsAccountID),\n\t\t\tForce:          awssdk.Bool(true),\n\t\t})\n\t\trequire.NoError(t, err)\n\t})\n\n\tdefer test_structure.RunTestStage(t, \"cleanup_k8s\", func() {\n\t\t// Return the output before cleanup - helps in debugging\n\t\tk8s.RunKubectl(t, kubectlOptions, \"logs\", \"--selector=app.kubernetes.io/name=k8s-image-swapper\", \"--tail=-1\")\n\t\thelm.Delete(t, &helm.Options{KubectlOptions: kubectlOptions}, releaseName, true)\n\t\tk8s.DeleteNamespace(t, kubectlOptions, namespaceName)\n\t})\n\n\ttest_structure.RunTestStage(t, \"build_and_load_docker_image\", func() {\n\t\t// Generate docker image to be tested\n\t\tshell.RunCommand(t, shell.Command{\n\t\t\tCommand:    \"goreleaser\",\n\t\t\tArgs:       []string{\"release\", \"--snapshot\", \"--skip-publish\", \"--rm-dist\"},\n\t\t\tWorkingDir: workingDir,\n\t\t})\n\n\t\t// Tag with \"local\" to ensure kind is not pulling from the GitHub Registry\n\t\tshell.RunCommand(t, shell.Command{\n\t\t\tCommand: \"docker\",\n\t\t\tArgs:    []string{\"tag\", \"ghcr.io/estahn/k8s-image-swapper:latest\", \"local/k8s-image-swapper:latest\"},\n\t\t})\n\n\t\t// Load generated docker image into kind\n\t\tshell.RunCommand(t, shell.Command{\n\t\t\tCommand: \"kind\",\n\t\t\tArgs:    []string{\"load\", \"docker-image\", \"local/k8s-image-swapper:latest\"},\n\t\t})\n\t})\n\n\ttest_structure.RunTestStage(t, \"deploy_webhook\", func() {\n\t\tk8s.CreateNamespace(t, kubectlOptions, namespaceName)\n\n\t\t// Setup permissions for kind to be able to pull from ECR\n\t\tecrAuthToken, _ := ecrClient.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{})\n\t\tecrDecodedAuthToken, _ := base64.StdEncoding.DecodeString(*ecrAuthToken.AuthorizationData[0].AuthorizationToken)\n\t\tecrUsernamePassword := bytes.Split(ecrDecodedAuthToken, []byte(\":\"))\n\n\t\tsecretName := awsRegion + \"-ecr-registry\"\n\t\tk8s.RunKubectl(t, kubectlOptions, \"create\", \"secret\", \"docker-registry\",\n\t\t\tsecretName,\n\t\t\t\"--docker-server=\"+*ecrAuthToken.AuthorizationData[0].ProxyEndpoint,\n\t\t\t\"--docker-username=\"+string(ecrUsernamePassword[0]),\n\t\t\t\"--docker-password=\"+string(ecrUsernamePassword[1]),\n\t\t\t\"--docker-email=anymail.doesnt.matter@email.com\",\n\t\t)\n\t\tk8s.RunKubectl(t, kubectlOptions, \"patch\", \"serviceaccount\", \"default\", \"-p\",\n\t\t\tfmt.Sprintf(\"{\\\"imagePullSecrets\\\":[{\\\"name\\\":\\\"%s\\\"}]}\", secretName),\n\t\t)\n\n\t\t// Setup the args. For this test, we will set the following input values:\n\t\toptions := &helm.Options{\n\t\t\tKubectlOptions: kubectlOptions,\n\t\t\tSetValues: map[string]string{\n\t\t\t\t\"config.logFormat\":                  \"console\",\n\t\t\t\t\"config.logLevel\":                   \"debug\",\n\t\t\t\t\"config.dryRun\":                     \"false\",\n\t\t\t\t\"config.target.aws.accountId\":       awsAccountID,\n\t\t\t\t\"config.target.aws.region\":          awsRegion,\n\t\t\t\t\"config.imageSwapPolicy\":            \"always\",\n\t\t\t\t\"config.imageCopyPolicy\":            \"delayed\",\n\t\t\t\t\"config.source.filters[0].jmespath\": \"obj.metadata.name != 'nginx'\",\n\t\t\t\t\"awsSecretName\":                     \"k8s-image-swapper-aws\",\n\t\t\t\t\"image.repository\":                  \"local/k8s-image-swapper\",\n\t\t\t\t\"image.tag\":                         \"latest\",\n\t\t\t},\n\t\t}\n\n\t\tk8s.RunKubectl(t, kubectlOptions, \"create\", \"secret\", \"generic\", \"k8s-image-swapper-aws\",\n\t\t\tfmt.Sprintf(\"--from-literal=aws_access_key_id=%s\", awsAccessKeyID),\n\t\t\tfmt.Sprintf(\"--from-literal=aws_secret_access_key=%s\", awsSecretAccessKey),\n\t\t)\n\n\t\t// Deploy the chart using `helm install`. Note that we use the version without `E`, since we want to assert the\n\t\t// install succeeds without any errors.\n\t\thelm.Install(t, options, \"estahn/k8s-image-swapper\", releaseName)\n\t})\n\n\ttest_structure.RunTestStage(t, \"validate\", func() {\n\t\tk8s.WaitUntilNumPodsCreated(t, kubectlOptions, metav1.ListOptions{LabelSelector: \"app.kubernetes.io/name=k8s-image-swapper\"}, 1, 30, 10*time.Second)\n\t\tk8s.WaitUntilServiceAvailable(t, kubectlOptions, releaseName, 30, 10*time.Second)\n\n\t\t// Launch nginx container to verify functionality\n\t\tk8s.RunKubectl(t, kubectlOptions, \"run\", \"nginx\", \"--image=nginx\", \"--restart=Never\")\n\t\tk8s.WaitUntilPodAvailable(t, kubectlOptions, \"nginx\", 30, 10*time.Second)\n\n\t\t// Verify container is running with images from ECR.\n\t\t// Implicit proof for repository creation and images pull/push via k8s-image-swapper.\n\t\tnginxPod := k8s.GetPod(t, kubectlOptions, \"nginx\")\n\n\t\trequire.Equal(t, ecrRegistry+\"/\"+ecrRepository+\":latest\", nginxPod.Spec.Containers[0].Image, \"container should be prefixed with ECR address\")\n\t})\n}\n\ntype sensitiveLogger struct {\n\tlogger   logger.TestLogger\n\tpatterns []*regexp.Regexp\n}\n\nfunc newSensitiveLogger(logger *logger.Logger, patterns []*regexp.Regexp) *sensitiveLogger {\n\treturn &sensitiveLogger{\n\t\tlogger:   logger,\n\t\tpatterns: patterns,\n\t}\n}\n\nfunc (l *sensitiveLogger) Logf(t terratesttesting.TestingT, format string, args ...interface{}) {\n\tvar redactedArgs []interface{}\n\n\tobfuscateWith := \"$1*******\"\n\n\tredactedArgs = args\n\n\tfor _, pattern := range l.patterns {\n\t\tfor i, arg := range redactedArgs {\n\t\t\tswitch arg := arg.(type) {\n\t\t\tcase string:\n\t\t\t\tredactedArgs[i] = pattern.ReplaceAllString(arg, obfuscateWith)\n\t\t\tcase []string:\n\t\t\t\tvar result []string\n\t\t\t\tfor _, s := range arg {\n\t\t\t\t\tresult = append(result, pattern.ReplaceAllString(s, obfuscateWith))\n\t\t\t\t}\n\t\t\t\tredactedArgs[i] = result\n\t\t\tdefault:\n\t\t\t\tpanic(\"type needs implementation\")\n\t\t\t}\n\t\t}\n\t}\n\n\tl.logger.Logf(t, format, redactedArgs...)\n}\n"
  },
  {
    "path": "test/kind-with-registry.sh",
    "content": "#!/bin/sh\nset -o errexit\n\n# create registry container unless it already exists\nreg_name='kind-registry'\nreg_port='5000'\nrunning=\"$(docker inspect -f '{{.State.Running}}' \"${reg_name}\" 2>/dev/null || true)\"\nif [ \"${running}\" != 'true' ]; then\n  docker run \\\n    -d --restart=always -p \"127.0.0.1:${reg_port}:5000\" --name \"${reg_name}\" \\\n    registry:2\nfi\n\n# create a cluster with the local registry enabled in containerd\nenvsubst < test/kind.yaml | kind create cluster --config=-\n\n# connect the registry to the cluster network\n# (the network may already be connected)\ndocker network connect \"kind\" \"${reg_name}\" || true\n\n# Document the local registry\n# https://github.com/kubernetes/enhancements/tree/master/keps/sig-cluster-lifecycle/generic/1755-communicating-a-local-registry\ncat <<EOF | kubectl apply -f -\napiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: local-registry-hosting\n  namespace: kube-public\ndata:\n  localRegistryHosting.v1: |\n    host: \"localhost:${reg_port}\"\n      help: \"https://kind.sigs.k8s.io/docs/user/local-registry/\"\nEOF\n"
  },
  {
    "path": "test/kind.yaml",
    "content": "---\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\ncontainerdConfigPatches:\n  - |-\n    [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"localhost:${reg_port}\"]\n      endpoint = [\"http://${reg_name}:${reg_port}\"]\nkubeadmConfigPatches:\n  - |\n    apiVersion: kubeadm.k8s.io/v1beta2\n    kind: ClusterConfiguration\n    metadata:\n      name: config\n    apiServer:\n      extraArgs:\n        \"enable-admission-plugins\": \"NamespaceLifecycle,LimitRanger,ServiceAccount,TaintNodesByCondition,Priority,DefaultTolerationSeconds,DefaultStorageClass,PersistentVolumeClaimResize,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota\"\n#nodes:\n#  - role: control-plane\n#    image: kindest/node:v1.16.4@sha256:b91a2c2317a000f3a783489dfb755064177dbc3a0b2f4147d50f04825d016f55\n#  - role: worker\n#    image: kindest/node:v1.16.4@sha256:b91a2c2317a000f3a783489dfb755064177dbc3a0b2f4147d50f04825d016f55\n"
  },
  {
    "path": "test/requests/admissionreview-imagepullsecrets.json",
    "content": "{\n  \"apiVersion\": \"admission.k8s.io/v1\",\n  \"kind\": \"AdmissionReview\",\n  \"request\": {\n    \"dryRun\": false,\n    \"kind\": {\n      \"group\": \"\",\n      \"kind\": \"Pod\",\n      \"version\": \"v1\"\n    },\n    \"name\": \"nginx28\",\n    \"object\": {\n      \"apiVersion\": \"v1\",\n      \"kind\": \"Pod\",\n      \"metadata\": {\n        \"creationTimestamp\": null,\n        \"labels\": {\n          \"run\": \"nginx28\"\n        },\n        \"name\": \"nginx28\",\n        \"namespace\": \"test-ns\"\n      },\n      \"spec\": {\n        \"containers\": [\n          {\n            \"image\": \"nginx\",\n            \"imagePullPolicy\": \"Always\",\n            \"name\": \"nginx28\",\n            \"resources\": {},\n            \"terminationMessagePath\": \"/dev/termination-log\",\n            \"terminationMessagePolicy\": \"File\",\n            \"volumeMounts\": [\n              {\n                \"mountPath\": \"/var/run/secrets/kubernetes.io/serviceaccount\",\n                \"name\": \"default-token-fjtvr\",\n                \"readOnly\": true\n              }\n            ]\n          }\n        ],\n        \"dnsPolicy\": \"ClusterFirst\",\n        \"enableServiceLinks\": true,\n        \"imagePullSecrets\": [\n          {\n            \"name\": \"my-pod-secret\"\n          }\n        ],\n        \"priority\": 0,\n        \"restartPolicy\": \"Never\",\n        \"schedulerName\": \"default-scheduler\",\n        \"securityContext\": {},\n        \"serviceAccount\": \"my-service-account\",\n        \"serviceAccountName\": \"my-service-account\",\n        \"terminationGracePeriodSeconds\": 30,\n        \"tolerations\": [\n          {\n            \"effect\": \"NoExecute\",\n            \"key\": \"node.kubernetes.io/not-ready\",\n            \"operator\": \"Exists\",\n            \"tolerationSeconds\": 300\n          },\n          {\n            \"effect\": \"NoExecute\",\n            \"key\": \"node.kubernetes.io/unreachable\",\n            \"operator\": \"Exists\",\n            \"tolerationSeconds\": 300\n          }\n        ],\n        \"volumes\": [\n          {\n            \"name\": \"default-token-fjtvr\",\n            \"secret\": {\n              \"secretName\": \"default-token-fjtvr\"\n            }\n          }\n        ]\n      },\n      \"status\": {}\n    },\n    \"oldObject\": null,\n    \"operation\": \"CREATE\",\n    \"options\": {\n      \"apiVersion\": \"meta.k8s.io/v1\",\n      \"fieldManager\": \"kubectl-run\",\n      \"kind\": \"CreateOptions\"\n    },\n    \"requestKind\": {\n      \"group\": \"\",\n      \"kind\": \"Pod\",\n      \"version\": \"v1\"\n    },\n    \"requestResource\": {\n      \"group\": \"\",\n      \"resource\": \"pods\",\n      \"version\": \"v1\"\n    },\n    \"resource\": {\n      \"group\": \"\",\n      \"resource\": \"pods\",\n      \"version\": \"v1\"\n    },\n    \"uid\": \"c78e0c58-7389-4838-b4f5-28d6005c1cc3\",\n    \"userInfo\": {\n      \"groups\": [\n        \"system:masters\",\n        \"system:authenticated\"\n      ],\n      \"username\": \"kubernetes-admin\"\n    }\n  }\n}\n"
  },
  {
    "path": "test/requests/admissionreview-simple.json",
    "content": "{\n  \"apiVersion\": \"admission.k8s.io/v1\",\n  \"kind\": \"AdmissionReview\",\n  \"request\": {\n    \"dryRun\": false,\n    \"kind\": {\n      \"group\": \"\",\n      \"kind\": \"Pod\",\n      \"version\": \"v1\"\n    },\n    \"name\": \"nginx28\",\n    \"object\": {\n      \"apiVersion\": \"v1\",\n      \"kind\": \"Pod\",\n      \"metadata\": {\n        \"creationTimestamp\": null,\n        \"labels\": {\n          \"run\": \"nginx28\"\n        },\n        \"name\": \"nginx28\",\n        \"namespace\": \"default\"\n      },\n      \"spec\": {\n        \"containers\": [\n          {\n            \"image\": \"nginx\",\n            \"imagePullPolicy\": \"Always\",\n            \"name\": \"nginx28\",\n            \"resources\": {},\n            \"terminationMessagePath\": \"/dev/termination-log\",\n            \"terminationMessagePolicy\": \"File\",\n            \"volumeMounts\": [\n              {\n                \"mountPath\": \"/var/run/secrets/kubernetes.io/serviceaccount\",\n                \"name\": \"default-token-fjtvr\",\n                \"readOnly\": true\n              }\n            ]\n          },\n          {\n            \"image\": \"k8s.gcr.io/ingress-nginx/controller:v0.43.0@sha256:9bba603b99bf25f6d117cf1235b6598c16033ad027b143c90fa5b3cc583c5713\",\n            \"imagePullPolicy\": \"Always\",\n            \"name\": \"ingress-nginx28\",\n            \"resources\": {},\n            \"terminationMessagePath\": \"/dev/termination-log\",\n            \"terminationMessagePolicy\": \"File\",\n            \"volumeMounts\": [\n              {\n                \"mountPath\": \"/var/run/secrets/kubernetes.io/serviceaccount\",\n                \"name\": \"default-token-fjtvr\",\n                \"readOnly\": true\n              }\n            ]\n          },\n          {\n            \"image\": \"123456789.dkr.ecr.ap-southeast-2.amazonaws.com/k8s.gcr.io/ingress-nginx/controller:v0.43.0@sha256:9bba603b99bf25f6d117cf1235b6598c16033ad027b143c90fa5b3cc583c5713\",\n            \"imagePullPolicy\": \"Always\",\n            \"name\": \"skip-test-ecr\",\n            \"resources\": {},\n            \"terminationMessagePath\": \"/dev/termination-log\",\n            \"terminationMessagePolicy\": \"File\",\n            \"volumeMounts\": [\n              {\n                \"mountPath\": \"/var/run/secrets/kubernetes.io/serviceaccount\",\n                \"name\": \"default-token-fjtvr\",\n                \"readOnly\": true\n              }\n            ]\n          },\n          {\n            \"image\": \"us-central1-docker.pkg.dev/gcp-project-123/main/k8s.gcr.io/ingress-nginx/controller@sha256:9bba603b99bf25f6d117cf1235b6598c16033ad027b143c90fa5b3cc583c5713\",\n            \"imagePullPolicy\": \"Always\",\n            \"name\": \"skip-test-gar\",\n            \"resources\": {},\n            \"terminationMessagePath\": \"/dev/termination-log\",\n            \"terminationMessagePolicy\": \"File\",\n            \"volumeMounts\": [\n              {\n                \"mountPath\": \"/var/run/secrets/kubernetes.io/serviceaccount\",\n                \"name\": \"default-token-fjtvr\",\n                \"readOnly\": true\n              }\n            ]\n          }\n        ],\n        \"dnsPolicy\": \"ClusterFirst\",\n        \"enableServiceLinks\": true,\n        \"initContainers\": [\n          {\n            \"image\": \"init-container\",\n            \"imagePullPolicy\": \"Always\",\n            \"name\": \"init-container28\",\n            \"resources\": {},\n            \"terminationMessagePath\": \"/dev/termination-log\",\n            \"terminationMessagePolicy\": \"File\",\n            \"volumeMounts\": [\n              {\n                \"mountPath\": \"/var/run/secrets/kubernetes.io/serviceaccount\",\n                \"name\": \"default-token-fxbar\",\n                \"readOnly\": true\n              }\n            ]\n          }\n        ],\n        \"priority\": 0,\n        \"restartPolicy\": \"Never\",\n        \"schedulerName\": \"default-scheduler\",\n        \"securityContext\": {},\n        \"serviceAccount\": \"default\",\n        \"serviceAccountName\": \"default\",\n        \"terminationGracePeriodSeconds\": 30,\n        \"tolerations\": [\n          {\n            \"effect\": \"NoExecute\",\n            \"key\": \"node.kubernetes.io/not-ready\",\n            \"operator\": \"Exists\",\n            \"tolerationSeconds\": 300\n          },\n          {\n            \"effect\": \"NoExecute\",\n            \"key\": \"node.kubernetes.io/unreachable\",\n            \"operator\": \"Exists\",\n            \"tolerationSeconds\": 300\n          }\n        ],\n        \"volumes\": [\n          {\n            \"name\": \"default-token-fjtvr\",\n            \"secret\": {\n              \"secretName\": \"default-token-fjtvr\"\n            }\n          }\n        ]\n      },\n      \"status\": {}\n    },\n    \"oldObject\": null,\n    \"operation\": \"CREATE\",\n    \"options\": {\n      \"apiVersion\": \"meta.k8s.io/v1\",\n      \"fieldManager\": \"kubectl-run\",\n      \"kind\": \"CreateOptions\"\n    },\n    \"requestKind\": {\n      \"group\": \"\",\n      \"kind\": \"Pod\",\n      \"version\": \"v1\"\n    },\n    \"requestResource\": {\n      \"group\": \"\",\n      \"resource\": \"pods\",\n      \"version\": \"v1\"\n    },\n    \"resource\": {\n      \"group\": \"\",\n      \"resource\": \"pods\",\n      \"version\": \"v1\"\n    },\n    \"uid\": \"c78e0c58-7389-4838-b4f5-28d6005c1cc3\",\n    \"userInfo\": {\n      \"groups\": [\n        \"system:masters\",\n        \"system:authenticated\"\n      ],\n      \"username\": \"kubernetes-admin\"\n    }\n  }\n}\n"
  }
]